From 9fe864355207145571efeb823d7b8194a3018f12 Mon Sep 17 00:00:00 2001 From: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Tue, 12 May 2026 09:58:37 -0700 Subject: [PATCH 1/6] chore: update checkout action configuration in workflows (#26897) --- .github/workflows/build-unsigned-mac-binaries.yml | 1 + .github/workflows/chained_e2e.yml | 4 ++++ .github/workflows/ci.yml | 10 ++++++++++ .github/workflows/deflake.yml | 3 +++ .github/workflows/docs-audit.yml | 1 + .github/workflows/docs-page-action.yml | 2 ++ .github/workflows/eval-pr.yml | 2 ++ .github/workflows/evals-nightly.yml | 4 ++++ .github/workflows/gemini-automated-issue-dedup.yml | 2 ++ .github/workflows/gemini-automated-issue-triage.yml | 2 ++ .github/workflows/gemini-cli-bot-pulse.yml | 1 + .github/workflows/gemini-lifecycle-manager.yml | 2 ++ .github/workflows/gemini-scheduled-issue-dedup.yml | 2 ++ .github/workflows/gemini-scheduled-issue-triage.yml | 2 ++ .github/workflows/gemini-scheduled-pr-triage.yml | 2 ++ .github/workflows/label-backlog-child-issues.yml | 4 ++++ .github/workflows/links.yml | 2 ++ .github/workflows/memory-nightly.yml | 2 ++ .github/workflows/perf-nightly.yml | 2 ++ .github/workflows/release-change-tags.yml | 1 + .github/workflows/release-manual.yml | 2 ++ .github/workflows/release-nightly.yml | 2 ++ .github/workflows/release-notes.yml | 1 + .github/workflows/release-patch-0-from-comment.yml | 1 + .github/workflows/release-patch-1-create-pr.yml | 1 + .github/workflows/release-patch-2-trigger.yml | 1 + .github/workflows/release-patch-3-release.yml | 2 ++ .github/workflows/release-promote.yml | 8 ++++++++ .github/workflows/release-rollback.yml | 1 + .github/workflows/release-sandbox.yml | 1 + .github/workflows/smoke-test.yml | 1 + .github/workflows/test-build-binary.yml | 2 ++ .github/workflows/verify-release.yml | 2 ++ 33 files changed, 76 insertions(+) diff --git a/.github/workflows/build-unsigned-mac-binaries.yml b/.github/workflows/build-unsigned-mac-binaries.yml index 9a5e58e92c..2acd67585e 100644 --- a/.github/workflows/build-unsigned-mac-binaries.yml +++ b/.github/workflows/build-unsigned-mac-binaries.yml @@ -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 diff --git a/.github/workflows/chained_e2e.yml b/.github/workflows/chained_e2e.yml index 4a5de8bf7c..a807fbfb37 100644 --- a/.github/workflows/chained_e2e.yml +++ b/.github/workflows/chained_e2e.yml @@ -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' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ef8bdb58d..5da8e6e05a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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' diff --git a/.github/workflows/deflake.yml b/.github/workflows/deflake.yml index a6a7d3664f..5d94dfc84e 100644 --- a/.github/workflows/deflake.yml +++ b/.github/workflows/deflake.yml @@ -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 diff --git a/.github/workflows/docs-audit.yml b/.github/workflows/docs-audit.yml index 4a2da6aa37..687bd3fb57 100644 --- a/.github/workflows/docs-audit.yml +++ b/.github/workflows/docs-audit.yml @@ -19,6 +19,7 @@ jobs: with: fetch-depth: 0 ref: 'main' + persist-credentials: false - name: 'Set up Node.js' uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' diff --git a/.github/workflows/docs-page-action.yml b/.github/workflows/docs-page-action.yml index be807c7c36..60554fb809 100644 --- a/.github/workflows/docs-page-action.yml +++ b/.github/workflows/docs-page-action.yml @@ -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 diff --git a/.github/workflows/eval-pr.yml b/.github/workflows/eval-pr.yml index 3e6784960c..1dab98b2ee 100644 --- a/.github/workflows/eval-pr.yml +++ b/.github/workflows/eval-pr.yml @@ -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 diff --git a/.github/workflows/evals-nightly.yml b/.github/workflows/evals-nightly.yml index 1fe61971fe..2ee064e4ae 100644 --- a/.github/workflows/evals-nightly.yml +++ b/.github/workflows/evals-nightly.yml @@ -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 diff --git a/.github/workflows/gemini-automated-issue-dedup.yml b/.github/workflows/gemini-automated-issue-dedup.yml index 0fe02b5530..27bc9f27fa 100644 --- a/.github/workflows/gemini-automated-issue-dedup.yml +++ b/.github/workflows/gemini-automated-issue-dedup.yml @@ -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 diff --git a/.github/workflows/gemini-automated-issue-triage.yml b/.github/workflows/gemini-automated-issue-triage.yml index e789aafa7d..f38988fecd 100644 --- a/.github/workflows/gemini-automated-issue-triage.yml +++ b/.github/workflows/gemini-automated-issue-triage.yml @@ -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' diff --git a/.github/workflows/gemini-cli-bot-pulse.yml b/.github/workflows/gemini-cli-bot-pulse.yml index b929444837..32fb6a0072 100644 --- a/.github/workflows/gemini-cli-bot-pulse.yml +++ b/.github/workflows/gemini-cli-bot-pulse.yml @@ -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' diff --git a/.github/workflows/gemini-lifecycle-manager.yml b/.github/workflows/gemini-lifecycle-manager.yml index 1de2565e8e..7f0a2b9484 100644 --- a/.github/workflows/gemini-lifecycle-manager.yml +++ b/.github/workflows/gemini-lifecycle-manager.yml @@ -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' diff --git a/.github/workflows/gemini-scheduled-issue-dedup.yml b/.github/workflows/gemini-scheduled-issue-dedup.yml index 46a6f4628b..b18ccf7fc0 100644 --- a/.github/workflows/gemini-scheduled-issue-dedup.yml +++ b/.github/workflows/gemini-scheduled-issue-dedup.yml @@ -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 diff --git a/.github/workflows/gemini-scheduled-issue-triage.yml b/.github/workflows/gemini-scheduled-issue-triage.yml index 6c8f10dcb7..570d806b91 100644 --- a/.github/workflows/gemini-scheduled-issue-triage.yml +++ b/.github/workflows/gemini-scheduled-issue-triage.yml @@ -30,6 +30,8 @@ jobs: steps: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 + with: + persist-credentials: false - name: 'Generate GitHub App Token' id: 'generate_token' diff --git a/.github/workflows/gemini-scheduled-pr-triage.yml b/.github/workflows/gemini-scheduled-pr-triage.yml index 50cd5a1bad..33072519b1 100644 --- a/.github/workflows/gemini-scheduled-pr-triage.yml +++ b/.github/workflows/gemini-scheduled-pr-triage.yml @@ -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' diff --git a/.github/workflows/label-backlog-child-issues.yml b/.github/workflows/label-backlog-child-issues.yml index 697e605d51..920fc1e4c3 100644 --- a/.github/workflows/label-backlog-child-issues.yml +++ b/.github/workflows/label-backlog-child-issues.yml @@ -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 diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml index 1ed45019f9..cbc5bb4f04 100644 --- a/.github/workflows/links.yml +++ b/.github/workflows/links.yml @@ -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' diff --git a/.github/workflows/memory-nightly.yml b/.github/workflows/memory-nightly.yml index ee4e5e589c..5a953999db 100644 --- a/.github/workflows/memory-nightly.yml +++ b/.github/workflows/memory-nightly.yml @@ -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 diff --git a/.github/workflows/perf-nightly.yml b/.github/workflows/perf-nightly.yml index 3749df231a..f45ab487e2 100644 --- a/.github/workflows/perf-nightly.yml +++ b/.github/workflows/perf-nightly.yml @@ -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 diff --git a/.github/workflows/release-change-tags.yml b/.github/workflows/release-change-tags.yml index 3a7c5648f8..09515f27d4 100644 --- a/.github/workflows/release-change-tags.yml +++ b/.github/workflows/release-change-tags.yml @@ -44,6 +44,7 @@ jobs: with: ref: '${{ github.ref }}' fetch-depth: 0 + persist-credentials: false - name: 'Setup Node.js' uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' diff --git a/.github/workflows/release-manual.yml b/.github/workflows/release-manual.yml index ec2a38b636..2a19aa1139 100644 --- a/.github/workflows/release-manual.yml +++ b/.github/workflows/release-manual.yml @@ -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 diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 9899e99d54..cf281deae4 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -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 diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml index bf0b4f42f2..d516ee928a 100644 --- a/.github/workflows/release-notes.yml +++ b/.github/workflows/release-notes.yml @@ -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' diff --git a/.github/workflows/release-patch-0-from-comment.yml b/.github/workflows/release-patch-0-from-comment.yml index 2bb7c27c7b..29a05884ad 100644 --- a/.github/workflows/release-patch-0-from-comment.yml +++ b/.github/workflows/release-patch-0-from-comment.yml @@ -17,6 +17,7 @@ jobs: - name: 'Checkout' uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' with: + persist-credentials: false fetch-depth: 1 - name: 'Slash Command Dispatch' diff --git a/.github/workflows/release-patch-1-create-pr.yml b/.github/workflows/release-patch-1-create-pr.yml index d19fc8e8b4..26b3eaeb6a 100644 --- a/.github/workflows/release-patch-1-create-pr.yml +++ b/.github/workflows/release-patch-1-create-pr.yml @@ -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 diff --git a/.github/workflows/release-patch-2-trigger.yml b/.github/workflows/release-patch-2-trigger.yml index 5976816dbc..8505f198f1 100644 --- a/.github/workflows/release-patch-2-trigger.yml +++ b/.github/workflows/release-patch-2-trigger.yml @@ -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 diff --git a/.github/workflows/release-patch-3-release.yml b/.github/workflows/release-patch-3-release.yml index 6680362a16..3dfb992a72 100644 --- a/.github/workflows/release-patch-3-release.yml +++ b/.github/workflows/release-patch-3-release.yml @@ -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 diff --git a/.github/workflows/release-promote.yml b/.github/workflows/release-promote.yml index e3a5100cfa..4ac5213a27 100644 --- a/.github/workflows/release-promote.yml +++ b/.github/workflows/release-promote.yml @@ -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' diff --git a/.github/workflows/release-rollback.yml b/.github/workflows/release-rollback.yml index db91457b1a..f23e6908b7 100644 --- a/.github/workflows/release-rollback.yml +++ b/.github/workflows/release-rollback.yml @@ -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 diff --git a/.github/workflows/release-sandbox.yml b/.github/workflows/release-sandbox.yml index 2c7de7a0f5..033ad45007 100644 --- a/.github/workflows/release-sandbox.yml +++ b/.github/workflows/release-sandbox.yml @@ -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' diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 29903dfbe8..41a9f927d6 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -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' diff --git a/.github/workflows/test-build-binary.yml b/.github/workflows/test-build-binary.yml index 05d6556f8c..e1ad5832ab 100644 --- a/.github/workflows/test-build-binary.yml +++ b/.github/workflows/test-build-binary.yml @@ -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'" diff --git a/.github/workflows/verify-release.yml b/.github/workflows/verify-release.yml index 20a9f51b8a..964d574081 100644 --- a/.github/workflows/verify-release.yml +++ b/.github/workflows/verify-release.yml @@ -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: From bc730b2c0fb1250cd83a4e5c28527615080953f1 Mon Sep 17 00:00:00 2001 From: Yulong Wu <50110323+TNTCompany@users.noreply.github.com> Date: Wed, 13 May 2026 01:02:15 +0800 Subject: [PATCH 2/6] fix (telemetry): inject quota_project_id to prevent fallback to default oauth client (#26698) Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Tommaso Sciortino --- packages/core/src/code_assist/oauth2.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts index 8ea83e5270..588a313ef9 100644 --- a/packages/core/src/code_assist/oauth2.ts +++ b/packages/core/src/code_assist/oauth2.ts @@ -60,6 +60,10 @@ async function triggerPostAuthCallbacks(tokens: Credentials) { refresh_token: tokens.refresh_token ?? undefined, // Ensure null is not passed type: 'authorized_user', client_email: userAccountManager.getCachedGoogleAccount() ?? undefined, + quota_project_id: + process.env['GOOGLE_CLOUD_QUOTA_PROJECT'] || + process.env['GOOGLE_CLOUD_PROJECT'] || + process.env['GOOGLE_CLOUD_PROJECT_ID'], }; // Execute all registered post-authentication callbacks. From ebe15553a9cea1ff27cf68e29dc7be96de86b0ee Mon Sep 17 00:00:00 2001 From: Sandy Tao Date: Tue, 12 May 2026 10:45:19 -0700 Subject: [PATCH 3/6] Exclude extension context from skill extraction agent (#26879) --- .../core/src/agents/local-executor.test.ts | 43 +++++++++++ packages/core/src/agents/local-executor.ts | 17 ++++- .../src/agents/skill-extraction-agent.test.ts | 1 + .../core/src/agents/skill-extraction-agent.ts | 1 + packages/core/src/agents/types.ts | 6 ++ packages/core/src/config/config.test.ts | 52 +++++++++++++ packages/core/src/config/config.ts | 73 +++++++++++++++++-- 7 files changed, 183 insertions(+), 10 deletions(-) diff --git a/packages/core/src/agents/local-executor.test.ts b/packages/core/src/agents/local-executor.test.ts index a35dc580b7..f8ddef3689 100644 --- a/packages/core/src/agents/local-executor.test.ts +++ b/packages/core/src/agents/local-executor.test.ts @@ -4199,6 +4199,49 @@ describe('LocalAgentExecutor', () => { expect(memoryPart).toBeDefined(); expect(memoryPart?.text).toContain(mockMemory); }); + + it('should omit extension context from session memory when disabled by the agent', async () => { + const definition = createTestDefinition(); + definition.includeExtensionContext = false; + const executor = await LocalAgentExecutor.create( + definition, + mockConfig, + onActivity, + ); + + const getSessionMemorySpy = vi + .spyOn(mockConfig, 'getSessionMemory') + .mockImplementation( + (options?: { includeExtensionContext?: boolean }) => + options?.includeExtensionContext === false + ? '\n\nProject memory rule\n\n' + : '\n\nExtension memory rule\n\n\nProject memory rule\n\n', + ); + vi.spyOn(mockConfig, 'isJitContextEnabled').mockReturnValue(true); + + mockModelResponse([ + { + name: COMPLETE_TASK_TOOL_NAME, + args: { finalResult: 'done' }, + id: 'call1', + }, + ]); + + await executor.run({ goal: 'test' }, signal); + + expect(getSessionMemorySpy).toHaveBeenCalledWith({ + includeExtensionContext: false, + }); + const { message } = getMockMessageParams(0); + const parts = message as Part[]; + const memoryPart = parts.find((p) => + p.text?.includes(''), + ); + + expect(memoryPart?.text).toContain('Project memory rule'); + expect(memoryPart?.text).not.toContain(''); + expect(memoryPart?.text).not.toContain('Extension memory rule'); + }); }); }); }); diff --git a/packages/core/src/agents/local-executor.ts b/packages/core/src/agents/local-executor.ts index 8780325ab8..4630ced5e5 100644 --- a/packages/core/src/agents/local-executor.ts +++ b/packages/core/src/agents/local-executor.ts @@ -640,10 +640,19 @@ export class LocalAgentExecutor { ); const formattedInitialHints = formatUserHintsForModel(initialHints); - // Inject loaded memory files (JIT + extension/project memory) - const environmentMemory = this.context.config.isJitContextEnabled?.() - ? this.context.config.getSessionMemory() - : this.context.config.getEnvironmentMemory(); + // Inject loaded memory files. Some background agents opt out of + // extension memory while still retaining project session context. + let environmentMemory: string; + if (this.context.config.isJitContextEnabled?.()) { + environmentMemory = + this.definition.includeExtensionContext === false + ? this.context.config.getSessionMemory({ + includeExtensionContext: false, + }) + : this.context.config.getSessionMemory(); + } else { + environmentMemory = this.context.config.getEnvironmentMemory(); + } const initialParts: Part[] = []; if (environmentMemory) { diff --git a/packages/core/src/agents/skill-extraction-agent.test.ts b/packages/core/src/agents/skill-extraction-agent.test.ts index 7e5251d053..fa9fc81caa 100644 --- a/packages/core/src/agents/skill-extraction-agent.test.ts +++ b/packages/core/src/agents/skill-extraction-agent.test.ts @@ -37,6 +37,7 @@ describe('SkillExtractionAgent', () => { expect(agent.modelConfig.model).toBe(PREVIEW_GEMINI_FLASH_MODEL); expect(agent.memoryInboxAccess).toBe(true); expect(agent.autoMemoryExtractionWriteAccess).toBe(true); + expect(agent.includeExtensionContext).toBe(false); expect(agent.toolConfig?.tools).toEqual( expect.arrayContaining([ READ_FILE_TOOL_NAME, diff --git a/packages/core/src/agents/skill-extraction-agent.ts b/packages/core/src/agents/skill-extraction-agent.ts index b84a46ba17..943626500a 100644 --- a/packages/core/src/agents/skill-extraction-agent.ts +++ b/packages/core/src/agents/skill-extraction-agent.ts @@ -415,6 +415,7 @@ export const SkillExtractionAgent = ( }, memoryInboxAccess: true, autoMemoryExtractionWriteAccess: true, + includeExtensionContext: false, toolConfig: { tools: [ ACTIVATE_SKILL_TOOL_NAME, diff --git a/packages/core/src/agents/types.ts b/packages/core/src/agents/types.ts index bfca8b81d6..86c9bec63b 100644 --- a/packages/core/src/agents/types.ts +++ b/packages/core/src/agents/types.ts @@ -244,6 +244,12 @@ export interface LocalAgentDefinition< */ autoMemoryExtractionWriteAccess?: boolean; + /** + * Controls whether extension memory is injected into this agent's initial + * session context when JIT context is enabled. Defaults to true. + */ + includeExtensionContext?: boolean; + /** * Optional inline MCP servers for this agent. */ diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 440cde681b..6696541107 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -3525,6 +3525,16 @@ describe('Config JIT Initialization', () => { expect(sessionMemory).toContain(''); expect(sessionMemory).toContain(''); + const sessionMemoryWithoutExtension = config.getSessionMemory({ + includeExtensionContext: false, + }); + expect(sessionMemoryWithoutExtension).toContain(''); + expect(sessionMemoryWithoutExtension).not.toContain(''); + expect(sessionMemoryWithoutExtension).not.toContain('Extension Memory'); + expect(sessionMemoryWithoutExtension).toContain(''); + expect(sessionMemoryWithoutExtension).toContain('Environment Memory'); + expect(sessionMemoryWithoutExtension).toContain(''); + // Verify state update (delegated to MemoryContextManager) expect(config.getGeminiMdFileCount()).toBe(1); expect(config.getGeminiMdFilePaths()).toEqual(['/path/to/GEMINI.md']); @@ -3746,6 +3756,8 @@ describe('Config JIT Initialization', () => { expect(config.isPathAllowed(privateExtractionPatch)).toBe(true); expect(config.validatePathAccess(privateExtractionPatch)).toBeNull(); expect(config.isPathAllowed(globalExtractionPatch)).toBe(true); + // Writes (the default checkType for isPathAllowed) remain restricted + // to the canonical extraction.patch filenames. expect( config.isPathAllowed(path.join(inboxRoot, 'private', 'other.patch')), ).toBe(false); @@ -3754,9 +3766,49 @@ describe('Config JIT Initialization', () => { path.join(inboxRoot, 'private', 'nested', 'extraction.patch'), ), ).toBe(false); + + // Reads are broadened to the .inbox/{private,global}/ subtree so the + // extractor can list and inspect prior patches before consolidating. + const privateOtherPatch = path.join( + inboxRoot, + 'private', + 'other.patch', + ); + const globalLeftover = path.join(inboxRoot, 'global', 'topic-a.patch'); + const nestedReadPath = path.join( + inboxRoot, + 'private', + 'nested', + 'extraction.patch', + ); + expect(config.validatePathAccess(privateOtherPatch, 'read')).toBeNull(); + expect(config.validatePathAccess(globalLeftover, 'read')).toBeNull(); + expect(config.validatePathAccess(nestedReadPath, 'read')).toBeNull(); + expect(config.validatePathAccess(inboxRoot, 'read')).toBeNull(); + expect( + config.validatePathAccess(path.join(inboxRoot, 'private'), 'read'), + ).toBeNull(); + expect( + config.validatePathAccess(path.join(inboxRoot, 'global'), 'read'), + ).toBeNull(); + + // Writes to the same broadened paths are still rejected. + expect(config.validatePathAccess(privateOtherPatch)).toContain( + 'Path not in workspace', + ); + expect(config.validatePathAccess(nestedReadPath)).toContain( + 'Path not in workspace', + ); }); expect(config.isPathAllowed(privateExtractionPatch)).toBe(false); + // Outside the scope, reads of inbox files are denied again. + expect( + config.validatePathAccess( + path.join(inboxRoot, 'private', 'other.patch'), + 'read', + ), + ).toContain('Path not in workspace'); }); it('should restrict scoped auto-memory extraction writes to generated artifacts', () => { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index f74ae4d7f5..b81737b0ea 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -2511,12 +2511,15 @@ export class Config implements McpContext, AgentLoopContext { * user message when JIT is enabled. Returns empty string when JIT is * disabled (Tier 2 memory is already in the system instruction). */ - getSessionMemory(): string { + getSessionMemory(options?: { includeExtensionContext?: boolean }): string { if (!this.experimentalJitContext || !this.memoryContextManager) { return ''; } const sections: string[] = []; - const extension = this.memoryContextManager.getExtensionMemory(); + const includeExtensionContext = options?.includeExtensionContext ?? true; + const extension = includeExtensionContext + ? this.memoryContextManager.getExtensionMemory() + : ''; const project = this.memoryContextManager.getEnvironmentMemory(); if (extension?.trim()) { sections.push( @@ -3088,12 +3091,49 @@ export class Config implements McpContext, AgentLoopContext { absolutePath: string, resolvedPath: string, inboxRoot: string, + checkType: 'read' | 'write' = 'write', ): boolean { if (!hasScopedMemoryInboxAccess()) { return false; } const normalizedPath = path.resolve(absolutePath); + const resolvedMemoryRoot = resolveToRealPath( + this.storage.getProjectMemoryTempDir(), + ); + + // Reads: allow the inbox root and the per-kind subtrees so the extraction + // agent can list/inspect prior patches (including non-canonical filenames + // left over from older runs) before deciding how to rewrite the canonical + // extraction.patch. Writes still flow through the strict canonical-path + // check below so the inbox cannot be backdoored with arbitrary files. + if (checkType === 'read') { + const resolvedInboxRoot = resolveToRealPath(inboxRoot); + const normalizedInboxRoot = path.resolve(inboxRoot); + if ( + resolvedPath === resolvedInboxRoot || + normalizedPath === normalizedInboxRoot + ) { + return isSubpath(resolvedMemoryRoot, resolvedPath); + } + + for (const kind of ['private', 'global'] as const) { + const kindRoot = path.join(inboxRoot, kind); + const resolvedKindRoot = resolveToRealPath(kindRoot); + const normalizedKindRoot = path.resolve(kindRoot); + if ( + resolvedPath === resolvedKindRoot || + normalizedPath === normalizedKindRoot || + isSubpath(resolvedKindRoot, resolvedPath) || + isSubpath(normalizedKindRoot, normalizedPath) + ) { + return isSubpath(resolvedMemoryRoot, resolvedPath); + } + } + + return false; + } + const isCanonicalPatchPath = (['private', 'global'] as const).some( (kind) => normalizedPath === path.resolve(inboxRoot, kind, 'extraction.patch'), @@ -3102,9 +3142,6 @@ export class Config implements McpContext, AgentLoopContext { return false; } - const resolvedMemoryRoot = resolveToRealPath( - this.storage.getProjectMemoryTempDir(), - ); return isSubpath(resolvedMemoryRoot, resolvedPath); } @@ -3148,7 +3185,9 @@ export class Config implements McpContext, AgentLoopContext { * the auto-memory extraction agent and the `/memory inbox` review flow. The * main agent is denied access to it even though it falls inside the project * temp dir; the extraction agent receives a narrow execution-scoped exception - * for `.inbox/{private,global}/extraction.patch`. + * for *writes* to `.inbox/{private,global}/extraction.patch`. Scoped *read* + * access to the wider `.inbox/{private,global}/` subtree is granted in + * `validatePathAccess` so the extractor can enumerate prior patches. * * @param absolutePath The absolute path to check. * @returns true if the path is allowed, false otherwise. @@ -3243,6 +3282,28 @@ export class Config implements McpContext, AgentLoopContext { if (this.getWorkspaceContext().isPathReadable(absolutePath)) { return null; } + + // The memory inbox is carved out of the standard temp-dir allowlist by + // `isPathAllowed`. The extraction agent is granted a scoped read + // exception so it can enumerate prior patches (including non-canonical + // filenames) before consolidating them into the canonical + // extraction.patch. Writes remain restricted to canonical paths. + if (hasScopedMemoryInboxAccess()) { + const inboxRoot = path.join( + this.storage.getProjectMemoryTempDir(), + '.inbox', + ); + if ( + this.isScopedMemoryInboxPatchPathAllowed( + absolutePath, + resolveToRealPath(absolutePath), + inboxRoot, + 'read', + ) + ) { + return null; + } + } } // Then check standard allowed paths (Workspace + Temp) From 27a39b04b0a600c45687c7e02340f9dae704c198 Mon Sep 17 00:00:00 2001 From: kevinjwang1 Date: Tue, 12 May 2026 11:06:21 -0700 Subject: [PATCH 4/6] Enable NumericalRouter when using dynamic model configs (#26929) --- packages/core/src/config/models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/config/models.ts b/packages/core/src/config/models.ts index 69541d1aca..684ded959e 100644 --- a/packages/core/src/config/models.ts +++ b/packages/core/src/config/models.ts @@ -345,7 +345,7 @@ export function isGemini3Model( ): boolean { if (config?.getExperimentalDynamicModelConfiguration?.() === true) { // Legacy behavior resolves the model first. - const resolved = resolveModel(model); + const resolved = resolveModel(model, false, false, false, true, config); return ( config.modelConfigService.getModelDefinition(resolved)?.family === 'gemini-3' From c4973d01da365dd79e87ffea9c8d84a0acfe05c1 Mon Sep 17 00:00:00 2001 From: Coco Sheng Date: Tue, 12 May 2026 14:33:55 -0400 Subject: [PATCH 5/6] ci: actively triage missing priority labels and intelligently clean up conflicting labels (#26865) --- .github/scripts/apply-issue-labels.cjs | 45 +++- .github/scripts/find-conflicting-labels.cjs | 60 +++++ .../gemini-scheduled-issue-triage.yml | 223 ++++++++++++++---- 3 files changed, 275 insertions(+), 53 deletions(-) create mode 100644 .github/scripts/find-conflicting-labels.cjs diff --git a/.github/scripts/apply-issue-labels.cjs b/.github/scripts/apply-issue-labels.cjs index 03c11403fe..f875a7ed38 100644 --- a/.github/scripts/apply-issue-labels.cjs +++ b/.github/scripts/apply-issue-labels.cjs @@ -85,14 +85,51 @@ module.exports = async ({ github, context, core }) => { continue; } - const labelsToAdd = entry.labels_to_add || []; - labelsToAdd.push('status/bot-triaged'); - + let labelsToAdd = entry.labels_to_add || []; let labelsToRemove = entry.labels_to_remove || []; + labelsToRemove.push('status/need-triage'); - // Deduplicate array + + if (labelsToAdd.includes('status/manual-triage')) { + // If the AI flagged it for manual triage, remove bot-triaged if it exists + labelsToRemove.push('status/bot-triaged'); + // Ensure we don't accidentally try to add bot-triaged if the AI returned it + labelsToAdd = labelsToAdd.filter((l) => l !== 'status/bot-triaged'); + } else { + // Standard successful bot triage + labelsToAdd.push('status/bot-triaged'); + } + + // Deduplicate arrays + labelsToAdd = [...new Set(labelsToAdd)]; labelsToRemove = [...new Set(labelsToRemove)]; + // Enforce mutually exclusive area labels + const areaLabelsToAdd = labelsToAdd.filter((l) => l.startsWith('area/')); + if (areaLabelsToAdd.length > 1) { + core.warning( + `Issue #${issueNumber} has multiple area labels to add: ${areaLabelsToAdd.join(', ')}. Keeping only the first one.`, + ); + const firstArea = areaLabelsToAdd[0]; + labelsToAdd = labelsToAdd.filter( + (l) => !l.startsWith('area/') || l === firstArea, + ); + } + + // Enforce mutually exclusive priority labels + const priorityLabelsToAdd = labelsToAdd.filter((l) => + l.startsWith('priority/'), + ); + if (priorityLabelsToAdd.length > 1) { + core.warning( + `Issue #${issueNumber} has multiple priority labels to add: ${priorityLabelsToAdd.join(', ')}. Keeping only the first one.`, + ); + const firstPriority = priorityLabelsToAdd[0]; + labelsToAdd = labelsToAdd.filter( + (l) => !l.startsWith('priority/') || l === firstPriority, + ); + } + if (labelsToAdd.length > 0) { await github.rest.issues.addLabels({ owner: context.repo.owner, diff --git a/.github/scripts/find-conflicting-labels.cjs b/.github/scripts/find-conflicting-labels.cjs new file mode 100644 index 0000000000..35b5e64e5a --- /dev/null +++ b/.github/scripts/find-conflicting-labels.cjs @@ -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`, + ); +}; diff --git a/.github/workflows/gemini-scheduled-issue-triage.yml b/.github/workflows/gemini-scheduled-issue-triage.yml index 570d806b91..66ed56cdb5 100644 --- a/.github/workflows/gemini-scheduled-issue-triage.yml +++ b/.github/workflows/gemini-scheduled-issue-triage.yml @@ -63,6 +63,16 @@ jobs: const syncIssueTypes = require('./.github/scripts/sync-issue-types.cjs'); await syncIssueTypes({ github, context, core }); + - name: 'Find Issues with Conflicting Labels' + if: |- + ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' + with: + github-token: '${{ steps.generate_token.outputs.token }}' + script: |- + const findConflictingLabels = require('./.github/scripts/find-conflicting-labels.cjs'); + await findConflictingLabels({ github, context, core }); + - name: 'Find untriaged issues' if: |- ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} @@ -83,22 +93,31 @@ jobs: echo '🏷️ Finding issues missing priority labels...' gh issue list --repo "${GITHUB_REPOSITORY}" \ - --search 'is:open is:issue -label:status/bot-triaged -label:priority/p0 -label:priority/p1 -label:priority/p2 -label:priority/p3 -label:priority/unknown' --limit 50 --json number,title,body > no_priority_issues.json + --search 'is:open is:issue -label:priority/p0 -label:priority/p1 -label:priority/p2 -label:priority/p3 -label:priority/unknown' --limit 50 --json number,title,body > no_priority_issues.json echo '📏 Finding issues missing effort labels...' gh issue list --repo "${GITHUB_REPOSITORY}" \ - --search 'is:open is:issue -label:status/bot-triaged -label:effort/small -label:effort/medium -label:effort/large label:area/core,area/extensions,area/site,area/non-interactive' --limit 20 --json number,title,body > no_effort_issues.json + --search 'is:open is:issue -label:effort/small -label:effort/medium -label:effort/large label:area/core,area/extensions,area/site,area/non-interactive' --limit 5 --json number,title,body > no_effort_issues.json - echo '🔄 Merging and deduplicating issues...' - jq -c -s 'add | unique_by(.number)' no_area_issues.json no_kind_issues.json no_priority_issues.json no_effort_issues.json no_type_issues.json > issues_to_triage.json + echo '🔄 Merging and deduplicating standard triage issues...' + if [ ! -f conflicting_labels_issues.json ]; then echo "[]" > conflicting_labels_issues.json; fi + jq -c -s 'add | unique_by(.number)' no_area_issues.json no_kind_issues.json no_priority_issues.json conflicting_labels_issues.json > standard_issues_to_triage.json - ISSUE_COUNT="$(jq 'length' issues_to_triage.json)" - if [ "$ISSUE_COUNT" -gt 0 ]; then + echo '📏 Deduplicating effort issues...' + jq -c -s 'add | unique_by(.number)' no_effort_issues.json > effort_issues_to_triage.json + + STANDARD_COUNT="$(jq 'length' standard_issues_to_triage.json)" + EFFORT_COUNT="$(jq 'length' effort_issues_to_triage.json)" + if [ "$STANDARD_COUNT" -gt 0 ] || [ "$EFFORT_COUNT" -gt 0 ]; then echo "has_issues=true" >> "${GITHUB_OUTPUT}" + echo "has_standard_issues=$([ "$STANDARD_COUNT" -gt 0 ] && echo 'true' || echo 'false')" >> "${GITHUB_OUTPUT}" + echo "has_effort_issues=$([ "$EFFORT_COUNT" -gt 0 ] && echo 'true' || echo 'false')" >> "${GITHUB_OUTPUT}" else echo "has_issues=false" >> "${GITHUB_OUTPUT}" + echo "has_standard_issues=false" >> "${GITHUB_OUTPUT}" + echo "has_effort_issues=false" >> "${GITHUB_OUTPUT}" fi - echo "✅ Found ${ISSUE_COUNT} unique issues to triage! 🎯" + echo "✅ Found ${STANDARD_COUNT} standard issues and ${EFFORT_COUNT} effort issues to triage! 🎯" - name: 'Create Gemini CLI Experiments Override' if: |- @@ -131,11 +150,128 @@ jobs: core.info(`Found ${labelNames.length} labels: ${labelNames.join(', ')}`); return labelNames; - - name: 'Run Gemini Issue Analysis' + - name: 'Run Standard Triage Analysis' if: |- - steps.get_issue_from_event.outputs.has_issues == 'true' || steps.find_issues.outputs.has_issues == 'true' + steps.get_issue_from_event.outputs.has_issues == 'true' || steps.find_issues.outputs.has_standard_issues == 'true' uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0 - id: 'gemini_issue_analysis' + id: 'gemini_standard_issue_analysis' + env: + GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs + REPOSITORY: '${{ github.repository }}' + AVAILABLE_LABELS: '${{ steps.get_labels.outputs.available_labels }}' + GEMINI_CLI_TRUST_WORKSPACE: 'true' + GEMINI_EXP: 'gemini_exp.json' + GEMINI_STRICT_TELEMETRY_LIMITS: 'true' + with: + gcp_workload_identity_provider: '${{ vars.GCP_WIF_PROVIDER }}' + gcp_project_id: '${{ vars.GOOGLE_CLOUD_PROJECT }}' + gcp_location: '${{ vars.GOOGLE_CLOUD_LOCATION }}' + gcp_service_account: '${{ vars.SERVICE_ACCOUNT_EMAIL }}' + gemini_api_key: '${{ secrets.GEMINI_API_KEY }}' + use_vertex_ai: '${{ vars.GOOGLE_GENAI_USE_VERTEXAI }}' + use_gemini_code_assist: '${{ vars.GOOGLE_GENAI_USE_GCA }}' + settings: |- + { + "maxSessionTurns": 25, + "coreTools": [ + "run_shell_command(echo)", + "read_file" + ], + "telemetry": { + "enabled": true, + "target": "gcp" + } + } + prompt: |- + ## Role + + You are an issue triage assistant. Analyze issues and identify + appropriate labels. Use the available tools to gather information; + do not ask for information to be provided. + + ## Steps + + 1. You are only able to use the echo and read_file commands. Review the available labels in the environment variable: "${AVAILABLE_LABELS}". + 2. Use the read_file tool to read the file "standard_issues_to_triage.json" which contains the JSON array of issues to triage. + 3. Review the issue title, body and any comments provided in the JSON file. + 4. Identify the most relevant labels from the existing labels, specifically focusing on area/*, kind/*, and priority/*. + 5. Label Policy: + - If the issue already has a kind/ label, do not change it. + - If the issue has exactly ONE priority/ label, do not change it. + - If the issue is missing a priority/ label, OR if the issue currently has MULTIPLE priority/ labels, you must evaluate the issue's impact to determine exactly ONE priority level (priority/p0, priority/p1, priority/p2, priority/p3, or priority/unknown) based the guidelines. If you are fixing an issue with multiple priority/ labels, put the correct one in `labels_to_add` and put all the incorrect ones in `labels_to_remove`. + - If the issue has exactly ONE area/ label, do not change it. + - If the issue is missing an area/ label, OR if the issue currently has MULTIPLE area/ labels, select exactly ONE area/ label that best fits the issue. Issues MUST NOT have multiple area/ labels. If you are fixing an issue with multiple area/ labels, put the correct one in `labels_to_add` and put all the incorrect ones in `labels_to_remove`. + - If any of these are missing, select exactly ONE appropriate label for the missing category. + 6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc. + 7. Give me a single short explanation about why you are selecting each label in the process. + 8. Output a JSON array of objects, each containing the issue number + and the labels to add and remove, along with an explanation. For example: + ``` + [ + { + "issue_number": 123, + "labels_to_add": ["area/core", "kind/bug", "priority/p2"], + "labels_to_remove": ["status/need-triage"], + "explanation": "This issue is a UI bug that needs to be addressed with medium priority." + } + ] + ``` + If an issue cannot be classified, do not include it in the output array. + 9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5 + - Anything more than 6 versions older than the most recent should add the status/need-retesting label + 10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below. + 11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation. + 12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label. + + ## Guidelines + + - Output only valid JSON format + - Do not include any explanation or additional text, just the JSON + - Only use labels that already exist in the repository. + - Do not add comments or modify the issue content. + - Do not remove the following labels maintainer, help wanted or good first issue. + - Triage only the current issue. + - Identify exactly ONE area/ label. Do NOT assign multiple area/ labels to a single issue. + - Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue) + - Identify exactly ONE priority/ label. Do NOT assign multiple priority/ labels to a single issue. + - Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario. + + Categorization Guidelines (Priority): + P0 - Urgent Blocking Issues: + - Definition: Critical failures breaking core functionality for a large portion of users. Examples: CLI fails to launch globally, core commands (gemini run) crash on valid input, unhandled promise rejections on boot, critical security vulnerability. + - Note: You must apply status/manual-triage instead of priority/p0. + P1 - Critical but Workable: + - Definition: Severe issues without a reasonable workaround, significantly degrading the developer experience but not globally blocking. Examples: Specific tools failing consistently (e.g., `web_search` returns 500s), persistent PTY streaming hangs, memory leaks leading to OOM after short use. + P2 - Significant Issues: + - Definition: Affect some workflows but a clear workaround exists, or non-critical bugs. Examples: Theme flickering, confusing error messages, minor UI misalignment, failing to read deeply nested config files correctly. + P3 - Minor/Enhancements: + - Definition: Trivial bugs, typos, documentation requests, or feature requests. + + Categorization Guidelines (Kind): + kind/bug: The issue is describing an unexpected behavior or failure in the application. + kind/enhancement: The issue is describing a feature request or an improvement to an existing feature. + kind/question: The issue is asking a question about how to use the CLI or about a specific feature. + + Categorization Guidelines (Area): + area/agent: The "brain" of the CLI. Core agent logic, model quality, tool/function calling, memory, web search, generated code quality, sub-agents. + area/core: The fundamental CLI app. UI/UX, installation, OS compatibility, performance, command parsing, theming, flickering. + area/documentation: Website docs, READMEs, inline help text. + area/enterprise: Telemetry, Policy, Quota / Licensing + area/extensions: Gemini CLI extensions capability + area/non-interactive: GitHub Actions, SDK, 3P Integrations, Shell Scripting, Command line automation + area/platform: Platform specific behavior + area/security: Authentication, authorization, privacy, data leaks, credential storage. + + - name: 'Stop Telemetry Collector' + if: |- + steps.find_issues.outputs.has_effort_issues == 'true' + run: 'docker rm -f gemini-telemetry-collector || true' + + - name: 'Run Effort Triage Analysis' + if: |- + steps.find_issues.outputs.has_effort_issues == 'true' + uses: 'google-github-actions/run-gemini-cli@a3bf79042542528e91937b3a3a6fbc4967ee3c31' # ratchet:google-github-actions/run-gemini-cli@v0 + id: 'gemini_effort_issue_analysis' env: GITHUB_TOKEN: '' # Do not pass any auth token here since this runs on untrusted inputs REPOSITORY: '${{ github.repository }}' @@ -168,57 +304,30 @@ jobs: prompt: |- ## Role - You are an issue triage assistant. Analyze issues and identify - appropriate labels. Use the available tools to gather information; - do not ask for information to be provided. + You are an expert software architect. Analyze the provided GitHub issues and assign the correct `effort/*` label based on the codebase complexity. ## Steps - 1. You are only able to use the echo and read_file commands. Review the available labels in the environment variable: "${AVAILABLE_LABELS}". - 2. Use the read_file tool to read the file "issues_to_triage.json" which contains the JSON array of issues to triage. - 3. Review the issue title, body and any comments provided in the JSON file. - 4. Identify the most relevant labels from the existing labels, specifically focusing on area/*, kind/*, priority/*, and effort/*. - 5. Label Policy: - - If the issue already has a kind/ label, do not change it. - - If the issue already has a priority/ label, do not change it. - - If the issue already has an area/ label, do not change it. - - If the issue already has an effort/ label, do not change it. - - If the issue is missing an effort/ label AND its area is area/core, area/extensions, area/site, or area/non-interactive, you must evaluate the architectural complexity to determine the effort level. You MUST NOT guess the root cause. You MUST actively use your codebase search tools (grep_search and glob) to search for keywords from the issue and explore the codebase. You must identify the specific files and components involved before deciding the effort. Do NOT evaluate or assign an effort/ label to issues in any other areas (such as area/agent). - - If any of these are missing, select exactly ONE appropriate label for the missing category. - 6. Identify other applicable labels based on the issue content, such as status/*, help wanted, good first issue, etc. - 7. Give me a single short explanation about why you are selecting each label in the process. - 8. Output a JSON array of objects, each containing the issue number - and the labels to add and remove, along with an explanation. If you assigned an effort/ label, you MUST also include an effort_analysis field. This effort_analysis must be highly detailed, technical, and empirical. It MUST NOT contain vague guesses (e.g., avoid words like "likely points to" or "possibly"). You must explicitly cite the specific file paths and architectural mechanisms you discovered using your search tools, explain the root cause, and then explicitly state how that complexity maps to the chosen effort level guidelines. For example: + 1. Use the read_file tool to read "effort_issues_to_triage.json". + 2. For each issue in the array: + - You must evaluate the architectural complexity to determine the effort level. You MUST NOT guess the root cause. You MUST actively use your codebase search tools (grep_search and glob) to search for keywords from the issue and explore the codebase. You must identify the specific files and components involved before deciding the effort. + 3. Output a JSON array of objects, each containing the issue number and the effort label to add, along with an explanation and an effort_analysis field. This effort_analysis must be highly detailed, technical, and empirical. It MUST NOT contain vague guesses (e.g., avoid words like "likely points to" or "possibly"). You must explicitly cite the specific file paths and architectural mechanisms you discovered using your search tools, explain the root cause, and then explicitly state how that complexity maps to the chosen effort level guidelines. For example: ``` [ { "issue_number": 123, - "labels_to_add": ["area/core", "kind/bug", "priority/p2", "effort/small"], - "labels_to_remove": ["status/need-triage"], - "explanation": "This issue is a UI bug that needs to be addressed with medium priority.", + "labels_to_add": ["effort/small"], + "explanation": "This is a simple logic fix.", "effort_analysis": "The `vscode-ide-companion` extension indiscriminately tracks active text editors via `vscode.window.onDidChangeActiveTextEditor` in `open-files-manager.ts`. When a user opens `.vscode/settings.json`, its content is sent to the CLI's context. The fix is highly localized to the VS Code companion extension's event listener. It involves adding a simple conditional check to exclude specific configuration files from the active editor tracking logic, which is a trivial logic adjustment with a clear root cause." } ] ``` - If an issue cannot be classified, do not include it in the output array. - 9. For each issue please check if CLI version is present, this is usually in the output of the /about command and will look like 0.1.5 - - Anything more than 6 versions older than the most recent should add the status/need-retesting label - 10. If you see that the issue doesn't look like it has sufficient information recommend the status/need-information label and leave a comment politely requesting the relevant information, eg.. if repro steps are missing request for repro steps. if version information is missing request for version information into the explanation section below. - 11. If you think an issue might be a Priority/P0 do not apply the priority/p0 label. Instead apply a status/manual-triage label and include a note in your explanation. - 12. If you are uncertain about a category, use the area/unknown, kind/question, or priority/unknown labels as appropriate. If you are extremely uncertain, apply the status/manual-triage label. ## Guidelines - Output only valid JSON format - Do not include any explanation or additional text, just the JSON - - Only use labels that already exist in the repository. - - Do not add comments or modify the issue content. - - Do not remove the following labels maintainer, help wanted or good first issue. - - Triage only the current issue. - - Identify only one area/ label. - - Identify only one kind/ label (Do not apply kind/duplicate or kind/parent-issue) - - Identify only one priority/ label. - - Once you categorize the issue if it needs information bump down the priority by 1 eg.. a p0 would become a p1 a p1 would become a p2. P2 and P3 can stay as is in this scenario. + - Triage only the current issue. Categorization Guidelines (Effort): effort/small (1 day or less): @@ -274,13 +383,29 @@ jobs: - This product is designed to use different models eg.. using pro, downgrading to flash etc. - When users report that they dont expect the model to change those would be categorized as feature requests. - - name: 'Apply Labels to Issues' + - name: 'Apply Standard Labels to Issues' if: |- - ${{ steps.gemini_issue_analysis.outcome == 'success' && - steps.gemini_issue_analysis.outputs.summary != '[]' }} + ${{ steps.gemini_standard_issue_analysis.outcome == 'success' && + steps.gemini_standard_issue_analysis.outputs.summary != '[]' && + steps.gemini_standard_issue_analysis.outputs.summary != '' }} env: REPOSITORY: '${{ github.repository }}' - LABELS_OUTPUT: '${{ steps.gemini_issue_analysis.outputs.summary }}' + LABELS_OUTPUT: '${{ steps.gemini_standard_issue_analysis.outputs.summary }}' + uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' + with: + github-token: '${{ steps.generate_token.outputs.token }}' + script: |- + const applyLabels = require('./.github/scripts/apply-issue-labels.cjs'); + await applyLabels({ github, context, core }); + + - name: 'Apply Effort Labels to Issues' + if: |- + ${{ steps.gemini_effort_issue_analysis.outcome == 'success' && + steps.gemini_effort_issue_analysis.outputs.summary != '[]' && + steps.gemini_effort_issue_analysis.outputs.summary != '' }} + env: + REPOSITORY: '${{ github.repository }}' + LABELS_OUTPUT: '${{ steps.gemini_effort_issue_analysis.outputs.summary }}' uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' with: github-token: '${{ steps.generate_token.outputs.token }}' From c987b99394a318e55095a18b8c1066a66edd91c7 Mon Sep 17 00:00:00 2001 From: Adam Weidman <65992621+adamfweidman@users.noreply.github.com> Date: Tue, 12 May 2026 14:58:25 -0400 Subject: [PATCH 6/6] refactor(core): introduce SubagentState enum for progress (#26934) --- .../messages/SubagentGroupDisplay.test.tsx | 14 ++++-- .../messages/SubagentGroupDisplay.tsx | 25 +++++----- .../messages/SubagentHistoryMessage.test.tsx | 7 +-- .../messages/SubagentProgressDisplay.test.tsx | 20 ++++---- .../messages/SubagentProgressDisplay.tsx | 27 ++++++----- .../ToolGroupMessageRegression.test.tsx | 5 +- .../cli/src/ui/hooks/useToolScheduler.test.ts | 13 ++++-- packages/core/src/agents/a2aUtils.ts | 8 ++-- .../agents/browser/browserAgentInvocation.ts | 46 +++++++++++-------- .../core/src/agents/local-invocation.test.ts | 17 +++---- packages/core/src/agents/local-invocation.ts | 44 ++++++++++-------- .../core/src/agents/remote-invocation.test.ts | 40 ++++++++++------ packages/core/src/agents/remote-invocation.ts | 11 +++-- .../src/agents/remote-subagent-protocol.ts | 5 +- packages/core/src/agents/types.ts | 11 ++++- 15 files changed, 172 insertions(+), 121 deletions(-) diff --git a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx index 484ca8a8ed..1a3572a82a 100644 --- a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx +++ b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.test.tsx @@ -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('', () => { 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('', () => { 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', }, ], diff --git a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.tsx b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.tsx index b57160966b..02ff8d461b 100644 --- a/packages/cli/src/ui/components/messages/SubagentGroupDisplay.tsx +++ b/packages/cli/src/ui/components/messages/SubagentGroupDisplay.tsx @@ -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 = ({ 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 = ({ 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 = ({ 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 = ({ } 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 !; - case 'completed': + case SubagentState.COMPLETED: return ; - case 'cancelled': + case SubagentState.CANCELLED: return ; - case 'error': + case SubagentState.ERROR: return ; default: return checkExhaustive(state); diff --git a/packages/cli/src/ui/components/messages/SubagentHistoryMessage.test.tsx b/packages/cli/src/ui/components/messages/SubagentHistoryMessage.test.tsx index 20a86cb5a9..9db757b240 100644 --- a/packages/cli/src/ui/components/messages/SubagentHistoryMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/SubagentHistoryMessage.test.tsx @@ -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, }, ], }; diff --git a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx index fcafa4ed28..d1f2d70f0e 100644 --- a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx +++ b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.test.tsx @@ -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('', () => { @@ -25,7 +25,7 @@ describe('', () => { type: 'tool_call', content: 'run_shell_command', args: '{"command": "echo hello", "description": "Say hello"}', - status: 'running', + status: SubagentState.RUNNING, }, ], }; @@ -48,7 +48,7 @@ describe('', () => { displayName: 'RunShellCommand', description: 'Executing echo hello', args: '{"command": "echo hello"}', - status: 'running', + status: SubagentState.RUNNING, }, ], }; @@ -69,7 +69,7 @@ describe('', () => { type: 'tool_call', content: 'run_shell_command', args: '{"command": "echo hello"}', - status: 'running', + status: SubagentState.RUNNING, }, ], }; @@ -90,7 +90,7 @@ describe('', () => { type: 'tool_call', content: 'write_file', args: '{"file_path": "/tmp/test.txt", "content": "foo"}', - status: 'completed', + status: SubagentState.COMPLETED, }, ], }; @@ -113,7 +113,7 @@ describe('', () => { type: 'tool_call', content: 'run_shell_command', args: JSON.stringify({ description: longDesc }), - status: 'running', + status: SubagentState.RUNNING, }, ], }; @@ -133,7 +133,7 @@ describe('', () => { id: '5', type: 'thought', content: 'Thinking about life', - status: 'running', + status: SubagentState.RUNNING, }, ], }; @@ -149,7 +149,7 @@ describe('', () => { isSubagentProgress: true, agentName: 'TestAgent', recentActivity: [], - state: 'cancelled', + state: SubagentState.CANCELLED, }; const { lastFrame } = await render( @@ -167,7 +167,7 @@ describe('', () => { id: '6', type: 'thought', content: 'Request cancelled.', - status: 'error', + status: SubagentState.ERROR, }, ], }; @@ -188,7 +188,7 @@ describe('', () => { type: 'tool_call', content: 'run_shell_command', args: '{"command": "echo hello"}', - status: 'error', + status: SubagentState.ERROR, }, ], }; diff --git a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx index 995c404d9d..b46756c5d3 100644 --- a/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx +++ b/packages/cli/src/ui/components/messages/SubagentProgressDisplay.tsx @@ -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 ? ( - ) : item.status === 'completed' ? ( + ) : item.status === SubagentState.COMPLETED ? ( {TOOL_STATUS.SUCCESS} - ) : item.status === 'cancelled' ? ( + ) : item.status === SubagentState.CANCELLED ? ( {TOOL_STATUS.CANCELED} @@ -135,7 +136,7 @@ export const SubagentProgressDisplay: React.FC< {item.displayName || item.content} @@ -144,7 +145,9 @@ export const SubagentProgressDisplay: React.FC< {displayArgs} @@ -170,7 +173,7 @@ export const SubagentProgressDisplay: React.FC< )} diff --git a/packages/cli/src/ui/components/messages/ToolGroupMessageRegression.test.tsx b/packages/cli/src/ui/components/messages/ToolGroupMessageRegression.test.tsx index 96239fb720..5206145c9e 100644 --- a/packages/cli/src/ui/components/messages/ToolGroupMessageRegression.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolGroupMessageRegression.test.tsx @@ -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: [], }, }), diff --git a/packages/cli/src/ui/hooks/useToolScheduler.test.ts b/packages/cli/src/ui/hooks/useToolScheduler.test.ts index efb9b8a6fd..e9665ec63b 100644 --- a/packages/cli/src/ui/hooks/useToolScheduler.test.ts +++ b/packages/cli/src/ui/hooks/useToolScheduler.test.ts @@ -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, + ); }); }); diff --git a/packages/core/src/agents/a2aUtils.ts b/packages/core/src/agents/a2aUtils.ts index 2d146fc420..876f623911 100644 --- a/packages/core/src/agents/a2aUtils.ts +++ b/packages/core/src/agents/a2aUtils.ts @@ -16,7 +16,7 @@ import type { AgentInterface, } from '@a2a-js/sdk'; import type { SendMessageResult } from './a2a-client-manager.js'; -import type { SubagentActivityItem } from './types.js'; +import { type SubagentActivityItem, SubagentState } from './types.js'; export const AUTH_REQUIRED_MSG = `[Authorization Required] The agent has indicated it requires authorization to proceed. Please follow the agent's instructions.`; @@ -143,7 +143,7 @@ export class A2AResultReassembler { id: 'auth-required', type: 'thought', content: AUTH_REQUIRED_MSG, - status: 'running', + status: SubagentState.RUNNING, }); } @@ -152,7 +152,7 @@ export class A2AResultReassembler { id: `msg-${index}`, type: 'thought', content: msg.trim(), - status: 'completed', + status: SubagentState.COMPLETED, }); }); @@ -161,7 +161,7 @@ export class A2AResultReassembler { id: 'pending', type: 'thought', content: 'Working...', - status: 'running', + status: SubagentState.RUNNING, }); } diff --git a/packages/core/src/agents/browser/browserAgentInvocation.ts b/packages/core/src/agents/browser/browserAgentInvocation.ts index a59ffc25b5..a27a8d29ed 100644 --- a/packages/core/src/agents/browser/browserAgentInvocation.ts +++ b/packages/core/src/agents/browser/browserAgentInvocation.ts @@ -32,6 +32,7 @@ import { type SubagentActivityItem, AgentTerminateMode, isToolActivityError, + SubagentState, } from '../types.js'; import type { MessageBus } from '../../confirmation-bus/message-bus.js'; import { createBrowserAgentDefinition } from './browserAgentFactory.js'; @@ -123,7 +124,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.agentName, recentActivity: [], - state: 'running', + state: SubagentState.RUNNING, }; updateOutput(initialProgress); } @@ -137,7 +138,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< id: randomUUID(), type: 'thought', content: sanitizedMsg, - status: 'completed', + status: SubagentState.COMPLETED, }); if (recentActivity.length > MAX_RECENT_ACTIVITY) { recentActivity = recentActivity.slice(-MAX_RECENT_ACTIVITY); @@ -146,7 +147,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.agentName, recentActivity: [...recentActivity], - state: 'running', + state: SubagentState.RUNNING, } as SubagentProgress); } : undefined; @@ -175,7 +176,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< if ( lastItem && lastItem.type === 'thought' && - lastItem.status === 'running' + lastItem.status === SubagentState.RUNNING ) { lastItem.content = sanitizeThoughtContent(text); } else { @@ -183,7 +184,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< id: randomUUID(), type: 'thought', content: sanitizeThoughtContent(text), - status: 'running', + status: SubagentState.RUNNING, }); } updated = true; @@ -210,7 +211,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< displayName, description, args, - status: 'running', + status: SubagentState.RUNNING, }); updated = true; break; @@ -227,9 +228,11 @@ export class BrowserAgentInvocation extends BaseToolInvocation< recentActivity[i].type === 'tool_call' && callId != null && recentActivity[i].id === callId && - recentActivity[i].status === 'running' + recentActivity[i].status === SubagentState.RUNNING ) { - recentActivity[i].status = isError ? 'error' : 'completed'; + recentActivity[i].status = isError + ? SubagentState.ERROR + : SubagentState.COMPLETED; updated = true; break; } @@ -242,7 +245,9 @@ export class BrowserAgentInvocation extends BaseToolInvocation< const callId = activity.data['callId'] ? String(activity.data['callId']) : undefined; - const newStatus = isCancellation ? 'cancelled' : 'error'; + const newStatus = isCancellation + ? SubagentState.CANCELLED + : SubagentState.ERROR; if (callId) { // Mark the specific tool as error/cancelled @@ -250,7 +255,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< if ( recentActivity[i].type === 'tool_call' && recentActivity[i].id === callId && - recentActivity[i].status === 'running' + recentActivity[i].status === SubagentState.RUNNING ) { recentActivity[i].status = newStatus; updated = true; @@ -260,7 +265,10 @@ export class BrowserAgentInvocation extends BaseToolInvocation< } else { // No specific tool — mark ALL running tool_call items for (const item of recentActivity) { - if (item.type === 'tool_call' && item.status === 'running') { + if ( + item.type === 'tool_call' && + item.status === SubagentState.RUNNING + ) { item.status = newStatus; updated = true; } @@ -293,7 +301,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.agentName, recentActivity: [...recentActivity], - state: 'running', + state: SubagentState.RUNNING, }; updateOutput(progress); } @@ -330,13 +338,13 @@ ${output.result}`; // GOAL = agent completed its task normally. // ABORTED = user cancelled. // Others (ERROR, MAX_TURNS, ERROR_NO_COMPLETE_TASK_CALL) = error. - let progressState: SubagentProgress['state']; + let progressState: SubagentState; if (output.terminate_reason === AgentTerminateMode.ABORTED) { - progressState = 'cancelled'; + progressState = SubagentState.CANCELLED; } else if (output.terminate_reason === AgentTerminateMode.GOAL) { - progressState = 'completed'; + progressState = SubagentState.COMPLETED; } else { - progressState = 'error'; + progressState = SubagentState.ERROR; } const progress: SubagentProgress = { @@ -366,8 +374,8 @@ ${output.result}`; // Mark any running items as error/cancelled for (const item of recentActivity) { - if (item.status === 'running') { - item.status = isAbort ? 'cancelled' : 'error'; + if (item.status === SubagentState.RUNNING) { + item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR; } } @@ -375,7 +383,7 @@ ${output.result}`; isSubagentProgress: true, agentName: this.agentName, recentActivity: [...recentActivity], - state: isAbort ? 'cancelled' : 'error', + state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR, }; if (updateOutput) { diff --git a/packages/core/src/agents/local-invocation.test.ts b/packages/core/src/agents/local-invocation.test.ts index eaea2b9ffa..297b46592e 100644 --- a/packages/core/src/agents/local-invocation.test.ts +++ b/packages/core/src/agents/local-invocation.test.ts @@ -21,6 +21,7 @@ import { type SubagentProgress, SubagentActivityErrorType, SUBAGENT_REJECTED_ERROR_PREFIX, + SubagentState, } from './types.js'; import { LocalSubagentInvocation } from './local-invocation.js'; import { LocalAgentExecutor } from './local-executor.js'; @@ -215,7 +216,7 @@ describe('LocalSubagentInvocation', () => { ]); const display = result.returnDisplay as SubagentProgress; expect(display.isSubagentProgress).toBe(true); - expect(display.state).toBe('completed'); + expect(display.state).toBe(SubagentState.COMPLETED); expect(display.result).toBe('Analysis complete.'); expect(display.terminateReason).toBe(AgentTerminateMode.GOAL); }); @@ -234,7 +235,7 @@ describe('LocalSubagentInvocation', () => { const display = result.returnDisplay as SubagentProgress; expect(display.isSubagentProgress).toBe(true); - expect(display.state).toBe('completed'); + expect(display.state).toBe(SubagentState.COMPLETED); expect(display.result).toBe('Partial progress...'); expect(display.terminateReason).toBe(AgentTerminateMode.TIMEOUT); }); @@ -340,7 +341,7 @@ describe('LocalSubagentInvocation', () => { expect.objectContaining({ type: 'thought', content: 'Error: Failed', - status: 'error', + status: SubagentState.ERROR, }), ); }); @@ -376,7 +377,7 @@ describe('LocalSubagentInvocation', () => { expect.objectContaining({ type: 'tool_call', content: 'ls', - status: 'error', + status: SubagentState.ERROR, }), ); }); @@ -418,7 +419,7 @@ describe('LocalSubagentInvocation', () => { expect.objectContaining({ type: 'tool_call', content: 'ls', - status: 'cancelled', + status: SubagentState.CANCELLED, }), ); }); @@ -443,7 +444,7 @@ describe('LocalSubagentInvocation', () => { expect(result.error).toBeUndefined(); const display = result.returnDisplay as SubagentProgress; expect(display.isSubagentProgress).toBe(true); - expect(display.state).toBe('completed'); + expect(display.state).toBe(SubagentState.COMPLETED); expect(display.result).toBe('Done'); }); @@ -466,7 +467,7 @@ describe('LocalSubagentInvocation', () => { expect.objectContaining({ type: 'thought', content: `Error: ${error.message}`, - status: 'error', + status: SubagentState.ERROR, }), ); }); @@ -488,7 +489,7 @@ describe('LocalSubagentInvocation', () => { expect(display.recentActivity).toContainEqual( expect.objectContaining({ content: `Error: ${creationError.message}`, - status: 'error', + status: SubagentState.ERROR, }), ); }); diff --git a/packages/core/src/agents/local-invocation.ts b/packages/core/src/agents/local-invocation.ts index 186f015979..f4d3153d79 100644 --- a/packages/core/src/agents/local-invocation.ts +++ b/packages/core/src/agents/local-invocation.ts @@ -23,6 +23,7 @@ import { SUBAGENT_REJECTED_ERROR_PREFIX, SUBAGENT_CANCELLED_ERROR_MESSAGE, isToolActivityError, + SubagentState, } from './types.js'; import { randomUUID } from 'node:crypto'; import type { z } from 'zod'; @@ -117,7 +118,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.definition.name, recentActivity: [], - state: 'running', + state: SubagentState.RUNNING, }; updateOutput(initialProgress); } @@ -137,7 +138,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< if ( lastItem && lastItem.type === 'thought' && - lastItem.status === 'running' + lastItem.status === SubagentState.RUNNING ) { lastItem.content = sanitizeThoughtContent(text); } else { @@ -145,7 +146,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< id: randomUUID(), type: 'thought', content: sanitizeThoughtContent(text), - status: 'running', + status: SubagentState.RUNNING, }); } updated = true; @@ -174,7 +175,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< displayName, description, args, - status: 'running', + status: SubagentState.RUNNING, }); updated = true; @@ -193,9 +194,11 @@ export class LocalSubagentInvocation extends BaseToolInvocation< if ( recentActivity[i].type === 'tool_call' && recentActivity[i].content === name && - recentActivity[i].status === 'running' + recentActivity[i].status === SubagentState.RUNNING ) { - recentActivity[i].status = isError ? 'error' : 'completed'; + recentActivity[i].status = isError + ? SubagentState.ERROR + : SubagentState.COMPLETED; updated = true; this.publishActivity(recentActivity[i]); @@ -224,9 +227,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation< if ( recentActivity[i].type === 'tool_call' && recentActivity[i].content === toolName && - recentActivity[i].status === 'running' + recentActivity[i].status === SubagentState.RUNNING ) { - recentActivity[i].status = 'cancelled'; + recentActivity[i].status = SubagentState.CANCELLED; updated = true; break; } @@ -237,9 +240,9 @@ export class LocalSubagentInvocation extends BaseToolInvocation< if ( recentActivity[i].type === 'tool_call' && recentActivity[i].content === toolName && - recentActivity[i].status === 'running' + recentActivity[i].status === SubagentState.RUNNING ) { - recentActivity[i].status = 'error'; + recentActivity[i].status = SubagentState.ERROR; updated = true; break; } @@ -253,7 +256,10 @@ export class LocalSubagentInvocation extends BaseToolInvocation< isCancellation || isRejection ? sanitizedError : `Error: ${sanitizedError}`, - status: isCancellation || isRejection ? 'cancelled' : 'error', + status: + isCancellation || isRejection + ? SubagentState.CANCELLED + : SubagentState.ERROR, }); updated = true; break; @@ -267,7 +273,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.definition.name, recentActivity: [...recentActivity], // Copy to avoid mutation issues - state: 'running', + state: SubagentState.RUNNING, }; updateOutput(progress); @@ -287,7 +293,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.definition.name, recentActivity: [...recentActivity], - state: 'cancelled', + state: SubagentState.CANCELLED, }; if (updateOutput) { @@ -303,7 +309,7 @@ export class LocalSubagentInvocation extends BaseToolInvocation< isSubagentProgress: true, agentName: this.definition.name, recentActivity: [...recentActivity], - state: 'completed', + state: SubagentState.COMPLETED, result: output.result, terminateReason: output.terminate_reason, }; @@ -334,8 +340,8 @@ ${output.result}`; // Mark any running items as error/cancelled for (const item of recentActivity) { - if (item.status === 'running') { - item.status = isAbort ? 'cancelled' : 'error'; + if (item.status === SubagentState.RUNNING) { + item.status = isAbort ? SubagentState.CANCELLED : SubagentState.ERROR; } } @@ -343,12 +349,12 @@ ${output.result}`; // But only if it's NOT an abort, or if we want to show "Cancelled" as a thought if (!isAbort) { const lastActivity = recentActivity[recentActivity.length - 1]; - if (!lastActivity || lastActivity.status !== 'error') { + if (!lastActivity || lastActivity.status !== SubagentState.ERROR) { recentActivity.push({ id: randomUUID(), type: 'thought', content: `Error: ${errorMessage}`, - status: 'error', + status: SubagentState.ERROR, }); // Maintain size limit // No limit on UI events sent via bus @@ -359,7 +365,7 @@ ${output.result}`; isSubagentProgress: true, agentName: this.definition.name, recentActivity: [...recentActivity], - state: isAbort ? 'cancelled' : 'error', + state: isAbort ? SubagentState.CANCELLED : SubagentState.ERROR, }; if (updateOutput) { diff --git a/packages/core/src/agents/remote-invocation.test.ts b/packages/core/src/agents/remote-invocation.test.ts index 0ec7774192..c2b89f49df 100644 --- a/packages/core/src/agents/remote-invocation.test.ts +++ b/packages/core/src/agents/remote-invocation.test.ts @@ -20,7 +20,11 @@ import { type A2AClientManager, } from './a2a-client-manager.js'; -import type { RemoteAgentDefinition, SubagentProgress } from './types.js'; +import { + type RemoteAgentDefinition, + type SubagentProgress, + SubagentState, +} from './types.js'; import { createMockMessageBus } from '../test-utils/mock-message-bus.js'; import { A2AAuthProviderFactory } from './auth-provider/factory.js'; import type { A2AAuthProvider } from './auth-provider/types.js'; @@ -268,7 +272,9 @@ describe('RemoteAgentInvocation', () => { abortSignal: new AbortController().signal, }); - expect(result.returnDisplay).toMatchObject({ state: 'error' }); + expect(result.returnDisplay).toMatchObject({ + state: SubagentState.ERROR, + }); expect((result.returnDisplay as SubagentProgress).result).toContain( "Failed to create auth provider for agent 'test-agent'", ); @@ -461,7 +467,7 @@ describe('RemoteAgentInvocation', () => { expect(updateOutput).toHaveBeenCalledWith( expect.objectContaining({ isSubagentProgress: true, - state: 'running', + state: SubagentState.RUNNING, recentActivity: expect.arrayContaining([ expect.objectContaining({ content: 'Working...' }), ]), @@ -470,7 +476,7 @@ describe('RemoteAgentInvocation', () => { expect(updateOutput).toHaveBeenCalledWith( expect.objectContaining({ isSubagentProgress: true, - state: 'completed', + state: SubagentState.COMPLETED, result: 'HelloHello World', }), ); @@ -508,7 +514,9 @@ describe('RemoteAgentInvocation', () => { abortSignal: controller.signal, }); - expect(result.returnDisplay).toMatchObject({ state: 'error' }); + expect(result.returnDisplay).toMatchObject({ + state: SubagentState.ERROR, + }); }); it('should handle errors gracefully', async () => { @@ -533,7 +541,7 @@ describe('RemoteAgentInvocation', () => { }); expect(result.returnDisplay).toMatchObject({ - state: 'error', + state: SubagentState.ERROR, result: expect.stringContaining('Network error'), }); }); @@ -616,7 +624,7 @@ describe('RemoteAgentInvocation', () => { expect(updateOutput).toHaveBeenCalledWith( expect.objectContaining({ isSubagentProgress: true, - state: 'running', + state: SubagentState.RUNNING, recentActivity: expect.arrayContaining([ expect.objectContaining({ content: 'Working...' }), ]), @@ -625,7 +633,7 @@ describe('RemoteAgentInvocation', () => { expect(updateOutput).toHaveBeenCalledWith( expect.objectContaining({ isSubagentProgress: true, - state: 'completed', + state: SubagentState.COMPLETED, result: 'Thinking...Final Answer', }), ); @@ -693,7 +701,7 @@ describe('RemoteAgentInvocation', () => { expect(updateOutput).toHaveBeenCalledWith( expect.objectContaining({ isSubagentProgress: true, - state: 'running', + state: SubagentState.RUNNING, recentActivity: expect.arrayContaining([ expect.objectContaining({ content: 'Working...' }), ]), @@ -702,7 +710,7 @@ describe('RemoteAgentInvocation', () => { expect(updateOutput).toHaveBeenCalledWith( expect.objectContaining({ isSubagentProgress: true, - state: 'completed', + state: SubagentState.COMPLETED, result: 'Generating...\n\nArtifact (Result):\nPart 1 Part 2', }), ); @@ -760,7 +768,9 @@ describe('RemoteAgentInvocation', () => { abortSignal: new AbortController().signal, }); - expect(result.returnDisplay).toMatchObject({ state: 'error' }); + expect(result.returnDisplay).toMatchObject({ + state: SubagentState.ERROR, + }); expect((result.returnDisplay as SubagentProgress).result).toContain( a2aError.userMessage, ); @@ -782,7 +792,9 @@ describe('RemoteAgentInvocation', () => { abortSignal: new AbortController().signal, }); - expect(result.returnDisplay).toMatchObject({ state: 'error' }); + expect(result.returnDisplay).toMatchObject({ + state: SubagentState.ERROR, + }); expect((result.returnDisplay as SubagentProgress).result).toContain( 'Error calling remote agent: something unexpected', ); @@ -813,7 +825,9 @@ describe('RemoteAgentInvocation', () => { abortSignal: new AbortController().signal, }); - expect(result.returnDisplay).toMatchObject({ state: 'error' }); + expect(result.returnDisplay).toMatchObject({ + state: SubagentState.ERROR, + }); // Should contain both the partial output and the error message expect(result.returnDisplay).toMatchObject({ result: expect.stringContaining('Partial response'), diff --git a/packages/core/src/agents/remote-invocation.ts b/packages/core/src/agents/remote-invocation.ts index e0869603fe..1510849683 100644 --- a/packages/core/src/agents/remote-invocation.ts +++ b/packages/core/src/agents/remote-invocation.ts @@ -17,6 +17,7 @@ import { type RemoteAgentDefinition, type AgentInputs, type SubagentProgress, + SubagentState, getAgentCardLoadOptions, getRemoteAgentTargetUrl, } from './types.js'; @@ -138,13 +139,13 @@ export class RemoteAgentInvocation extends BaseToolInvocation< updateOutput({ isSubagentProgress: true, agentName, - state: 'running', + state: SubagentState.RUNNING, recentActivity: [ { id: 'pending', type: 'thought', content: 'Working...', - status: 'running', + status: SubagentState.RUNNING, }, ], }); @@ -193,7 +194,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation< updateOutput({ isSubagentProgress: true, agentName, - state: 'running', + state: SubagentState.RUNNING, recentActivity: reassembler.toActivityItems(), result: reassembler.toString(), }); @@ -225,7 +226,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation< const finalProgress: SubagentProgress = { isSubagentProgress: true, agentName, - state: 'completed', + state: SubagentState.COMPLETED, result: finalOutput, recentActivity: reassembler.toActivityItems(), }; @@ -249,7 +250,7 @@ export class RemoteAgentInvocation extends BaseToolInvocation< const errorProgress: SubagentProgress = { isSubagentProgress: true, agentName, - state: 'error', + state: SubagentState.ERROR, result: fullDisplay, recentActivity: reassembler.toActivityItems(), }; diff --git a/packages/core/src/agents/remote-subagent-protocol.ts b/packages/core/src/agents/remote-subagent-protocol.ts index 4179e5587b..1231b0f068 100644 --- a/packages/core/src/agents/remote-subagent-protocol.ts +++ b/packages/core/src/agents/remote-subagent-protocol.ts @@ -28,6 +28,7 @@ import { DEFAULT_QUERY_STRING, type RemoteAgentDefinition, type SubagentProgress, + SubagentState, getRemoteAgentTargetUrl, getAgentCardLoadOptions, } from './types.js'; @@ -233,7 +234,7 @@ class RemoteSubagentProtocol implements AgentProtocol { this._latestProgress = { isSubagentProgress: true, agentName: this._agentName, - state: 'running', + state: SubagentState.RUNNING, recentActivity: reassembler.toActivityItems(), result: currentText, }; @@ -259,7 +260,7 @@ class RemoteSubagentProtocol implements AgentProtocol { const finalProgress: SubagentProgress = { isSubagentProgress: true, agentName: this._agentName, - state: 'completed', + state: SubagentState.COMPLETED, result: finalOutput, recentActivity: reassembler.toActivityItems(), }; diff --git a/packages/core/src/agents/types.ts b/packages/core/src/agents/types.ts index 86c9bec63b..7d99c10933 100644 --- a/packages/core/src/agents/types.ts +++ b/packages/core/src/agents/types.ts @@ -88,6 +88,13 @@ export interface SubagentActivityEvent { data: Record; } +export enum SubagentState { + RUNNING = 'running', + COMPLETED = 'completed', + ERROR = 'error', + CANCELLED = 'cancelled', +} + export interface SubagentActivityItem { id: string; type: 'thought' | 'tool_call'; @@ -95,14 +102,14 @@ export interface SubagentActivityItem { displayName?: string; description?: string; args?: string; - status: 'running' | 'completed' | 'error' | 'cancelled'; + status: SubagentState; } export interface SubagentProgress { isSubagentProgress: true; agentName: string; recentActivity: SubagentActivityItem[]; - state?: 'running' | 'completed' | 'error' | 'cancelled'; + state?: SubagentState; result?: string; terminateReason?: AgentTerminateMode; }