Skip to content
This repository was archived by the owner on May 20, 2026. It is now read-only.

Align Cloud Agent with Mission Control PR-decouple behavior#5078

Open
Oregand wants to merge 3 commits into
microsoft:mainfrom
Oregand:oregand/cloud-agent-pr-decouple
Open

Align Cloud Agent with Mission Control PR-decouple behavior#5078
Oregand wants to merge 3 commits into
microsoft:mainfrom
Oregand:oregand/cloud-agent-pr-decouple

Conversation

@Oregand

@Oregand Oregand commented Apr 17, 2026

Copy link
Copy Markdown

VS Code's Cloud Agent flow was still doing a bunch of pre-PR work that Mission Control / CCA PR Decoupling no longer need.

Problems

  1. A PR is created up front, with extra preflight work.
  2. A modal asks the user to delegate / commit changes / push branch. Codex and Claude don't show this.
  3. The agent behaves strangely in VS Code — fixates on copilot-instructions.md and appears to invent its own prompt.

The "strange prompt" behavior was caused by us summarizing the chat history and appending a body_suffix to problem_statement / pull_request.body_suffix before sending. Mission Control sends the raw prompt with a minimal pull_request block; this change matches that.

Changes

  • Drop the delegate/commit/push modal. New-request path in chatParticipantImpl goes straight to delegate(). Permissive GitHub auth now kicks in via the standard non-modal createIfNone sign-in prompt.
  • Send the raw user prompt. Stop calling IChatDelegationSummaryService.summarize (and trackSummaryUsage) on the delegation path. problem_statement is now the user prompt + resolved references only. extractPrompt usage for reading back PR bodies is unchanged.
  • Match MC's minimal pull_request payload. Drop body_placeholder and body_suffix — keep title, base_ref, and optional head_ref. Matches internal/sweagentd/sweagentd.go in github/copilot-mission-control.
  • Drop the pre-delegation git preflight. Removed detectedUncommittedChanges, checkBaseBranchPresentOnRemote, buildConfirmation, and handleConfirmationData. delegate() already falls back to the repo's default_branch when base_ref isn't supplied.
  • Delete dead code. CopilotCloudGitOperationsManager is now unreachable — removed the whole file. body_suffix / formatBodyPlaceholder removed from copilotCodingAgentUtils.ts. SEEN_DELEGATION_PROMPT_KEY, validateMetadata, hasHistoryToSummarize, and the modal string constants are also gone.

Regressions fixed during review

After self-audit + independent critique, four behavior regressions from the first pass were addressed in a follow-up commit:

  1. Current remote-tracking branch was ignored. The initial alignment commit always fell back to default_branch, which was broader than the unpushed-WIP case it was meant to cover. Now: if the active repo matches the selected repo and RepoContext.upstreamBranchName is set (branch exists on remote), headBranchName is used as base_ref. Only unpushed branches fall back.
  2. Auth cancellation gate only applied to the chat-participant entry point. copilotCLIChatSessions.ts and copilotCLIChatSessionsContribution.ts call delegate() directly; they would have surfaced a generic "invalid response" error on cancellation. The permissive-auth check now lives inside delegate() itself so every caller benefits.
  3. Uncommitted-changes warning was incomplete. CLI paths only checked indexChanges (staged); the main path warned on neither. delegate() and both CLI handlers now check both workingTree and indexChanges.
  4. Base-branch fallback was silent. Users had no way to know their work would run from default_branch instead of the current branch. A stream.progress notice now fires when that fallback happens.

Server-side verification

  • sweagentd docs/adr/0001-create-job-api.md: only problem_statement is required; pull_request is [Optional]; v1 POST /jobs returns {job_id, session_id, actor, created_at, updated_at} immediately, without waiting for the PR.
  • copilot-mission-control internal/sweagentd/sweagentd.go: MC sends pull_request = {title, base_ref, head_ref} only — no body_placeholder, no body_suffix, no history/summary injection in problem_statement. This is our target behavior.

Non-PR (no PR at all) workflow is on the sweagentd roadmap ("Supporting non-PR based workflow is on our radar but didn't get a chance to look into this yet"). This PR is forward-compatible: once the server supports it, we can drop pull_request entirely from the payload.

Tests

  • npx vitest run src/extension/chatSessions/vscode-node/test/ — 474 passed, 3 skipped, 14 test files.
  • npx tsc -p tsconfig.json --noEmit — clean.
  • npx eslint on the changed files — clean.

Existing spec (copilotCloudSessionsProvider.spec.ts) covers helpers + ChatSessionContentBuilder; dedicated coverage for the new delegate() paths (auth cancellation, upstream-branch detection, uncommitted-changes warning) is flagged as a follow-up rather than being bundled here.

Follow-ups (separate PRs)

  • Tasks API migration. Migrate from /jobs to Mission Control's public Tasks API (POST /repos/{owner}/{repo}/tasks), which defaults create_pull_request: false for public callers — aligning us with the non-PR workflow once @vscode/copilot-api exposes a client. OpenAPI confirms the default in api/generated-sessions-openapi.yaml.
  • W3b (server-dependent): true PR decoupling via a session-by-id URI scheme and a SessionIdForJob resource type, so nothing in the provider assumes a PR ever exists. Waits on sweagentd.
  • Consider dropping the 30s waitForJobWithPullRequest wait in delegate() in a follow-up once we're happy to return a streamed/placeholder ChatResponsePullRequestPart.
  • Add dedicated tests for the new delegate() paths (auth cancellation, upstream branch detection, uncommitted-changes warning).

Risk

Low. All touched code was reachable only from the modal path we're removing, from the summary path we're removing, or from the rich-PR-payload path that MC already proves is unnecessary. The authenticated (permissiveGitHubSession) pre-check keeps the same auth requirement — just without the modal wrapper.

The VS Code Cloud Agent experience was out of sync with Mission Control's
CCA PR-decoupling flow. This aligns VS Code with MC behavior:

- Drop the delegate / commit / push modal. Codex and Claude don't show
  one; Mission Control doesn't either. Permissive GitHub auth now uses
  the standard non-modal sign-in prompt via createIfNone.
- Send the raw user prompt as problem_statement (no chat-history
  summary injection). This fixes the 'agent invents its own prompt'
  behavior Luke reported.
- Send a minimal pull_request block (title/base_ref/[head_ref]) to
  match what sweagentd + Mission Control send. Drop body_placeholder
  and the 'Created from VS Code' body_suffix.
- Drop the pre-delegation git preflight (uncommitted-changes detection,
  remote base-branch check, branch push). delegate() already falls
  back to the repo's default_branch when base_ref is not supplied.
- Delete the now-unreachable CopilotCloudGitOperationsManager and
  the unused body_suffix / formatBodyPlaceholder helpers.

Server-side verified: sweagentd v1 API (docs/adr/0001-create-job-api.md)
makes pull_request optional and returns job_id + session_id before the
PR exists; Mission Control's sweagentd client sends the minimal
pull_request shape used here. Full no-PR (non-PR) workflow is on the
sweagentd roadmap; this change is compatible with it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 17, 2026 03:04
@Oregand
Oregand marked this pull request as draft April 17, 2026 03:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns the VS Code Copilot Cloud Agent delegation flow with Mission Control’s PR-decoupling behavior by removing pre-delegation UI/preflight work and sending a minimal job payload (raw user prompt + resolved references, minimal pull_request block).

Changes:

  • Removes the delegate/commit/push confirmation modal and associated git preflight / workspace-state tracking.
  • Stops summarizing chat history for problem_statement; sends the raw prompt with resolved references only.
  • Simplifies the pull_request payload to title, base_ref, and optional head_ref, and deletes now-unreachable git operations code.
Show a summary per file
File Description
src/extension/chatSessions/vscode/copilotCodingAgentUtils.ts Removes PR body placeholder/suffix helpers now that cloud sessions no longer send those fields.
src/extension/chatSessions/vscode-node/copilotCloudSessionsProvider.ts Removes confirmation + git preflight flow; delegates directly, removes history summarization, and trims pull_request payload fields.
src/extension/chatSessions/vscode-node/copilotCloudGitOperationsManager.ts Deletes unreachable git operations helper previously used by the modal/preflight path.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment on lines +1699 to +1703
await this._authenticationService.getGitHubSession('permissive', { createIfNone: { detail: l10n.t('Sign in to GitHub with additional permissions to use Copilot cloud sessions.') } });
} catch (error) {
this.logService.error(`Authorization failed: ${error}`);
throw new Error(vscode.l10n.t('Authorization failed. Please sign into GitHub and try again.'));
}

Copilot AI Apr 17, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new permissive-auth path treats any error from getGitHubSession('permissive', { createIfNone: … }) as an authorization failure. getGitHubSession can throw a cancellation error when the user dismisses the sign-in prompt, so this will surface as an error instead of cleanly cancelling the request. Consider handling isCancellationError(error) (and returning {} / streaming a cancellation message) separately, and log via logService.error(error, 'Authorization failed') rather than interpolating the error into the message (per ILogService.error guidance).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, both addressed in aecc4a7:

  • Cancellation is now re-thrown as a CancellationError when isCancellationError(error) (or token.isCancellationRequested) so a dismissed sign-in prompt cancels cleanly instead of surfacing as an auth failure.
  • Switched to logService.error(error, 'Authorization failed') per the ILogService.error(error: string | Error, message?: string) signature.

Address review feedback on PR microsoft#5078:
- Handle isCancellationError (and token.isCancellationRequested) by
  throwing CancellationError instead of the generic auth-failed error,
  so user-dismissed sign-in prompts cancel cleanly.
- Use logService.error(error, 'Authorization failed') per ILogService
  guidance rather than interpolating the error into the message string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Oregand

Oregand commented Apr 17, 2026

Copy link
Copy Markdown
Author

Next logical PR: migrate to Mission Control's public Tasks API

Today (both before and after this PR): VS Code calls sweagentd via /jobs through @vscode/copilot-api's RemoteAgentJobPayload, which requires pull_request semantics and always produces a PR.

What MC ships today: POST /repos/{owner}/{repo}/tasks (see api/generated-sessions-openapi.yaml and internal/session/handler_task.go):

create_pull_request:
  type: boolean
  default: false    # ← public callers get no-PR by default
// handler_task.go
// CreatePullRequest precedence:
// - Use the explicit request value when provided.
// - Otherwise default to false for public API requests.
// - Otherwise default to true for internal requests.

Session identity is a task_id (no PR required). Peer endpoints already exist: GET /tasks/{id}/events, POST /tasks/{id}/steer, POST /repos/{owner}/{repo}/tasks/{id}/archive, POST /repos/{owner}/{repo}/tasks/{id}/sessions, and create-pull-request-for-task for when the user explicitly asks for a PR later.

Proposed follow-up (stacked on this PR)

  1. Add a Tasks API client to @vscode/copilot-api. It currently only exposes RemoteAgentJobPayload — we need CreateTaskRequest / TaskResponse / events types.
  2. Swap session identity from PR number → task_id. Introduce SessionIdForTask; migrate SessionIdForPr usages in copilotCloudSessionsProvider.ts; provideChatSessionItems lists tasks instead of PRs.
  3. Replace waitForJobWithPullRequest with GET /tasks/{id}/events streaming.
  4. Gate PR-requiring UI (openPullRequestReroute, checkoutPullRequestReroute) on "task has a PR" and add a "Create PR" action that posts to /tasks/{id}/create-pull-request.
  5. Default create_pull_request: false on create — matches Codex and Claude behavior that Luke's Slack thread called out.
  6. Telemetry api: "tasks" | "jobs" during rollout, feature-flag the client-side switch for canarying.

That PR is the one that delivers full PR-decoupling end-to-end — this PR is the minimum viable step to stop the modal/preflight/summary work that's misaligned with Mission Control's contract today.

@Oregand
Oregand marked this pull request as ready for review April 17, 2026 13:59
@Oregand

Oregand commented Apr 17, 2026

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="GitHub"

Follow-up to the initial alignment commit. Independent review caught
four real issues; this addresses all of them:

1. Current remote-tracking branch was being ignored. delegate() now
   checks RepoContext.upstreamBranchName — if the user's checked-out
   branch is pushed to remote, use it as base_ref. If it's unpushed,
   fall back to default_branch and emit a progress notice so the user
   knows which branch the agent will start from.

2. Auth cancellation was only handled in chatParticipantImpl, leaving
   CLI /delegate callers (copilotCLIChatSessions.ts,
   copilotCLIChatSessionsContribution.ts) with a generic "invalid
   response" error when the user dismissed the sign-in prompt. Moved
   the permissive-session acquisition + CancellationError normalization
   into delegate() itself so every caller benefits.

3. Uncommitted-changes warning now fires from the shared delegate()
   path, covering both staged (indexChanges) and unstaged
   (workingTree) changes. Aligns with Mission Control / Codex / Claude
   behavior — warn the user, but don't block.

4. Base-branch fallback is no longer silent: when we fall back to
   default_branch because the current branch isn't on remote, emit a
   stream.progress message telling the user what will happen.

Also fixed the existing CLI-path uncommitted-changes checks in
copilotCLIChatSessions.ts:1463 and
copilotCLIChatSessionsContribution.ts:1742 which only looked at
indexChanges; now they check workingTree too.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Oregand

Oregand commented Apr 17, 2026

Copy link
Copy Markdown
Author

Did an independent critique pass after the initial round and it caught four real regressions that the first commit introduced. Pushed 86924b118 addressing all of them:

1. Current remote-tracking branch was being ignored. The old preflight path used the user's checked-out branch as base_ref when it existed on remote. The minimal-alignment commit always fell back to default_branch, which hit pushed feature branches too — broader than just the unpushed-WIP case. delegate() now inspects RepoContext.upstreamBranchName: if the branch is tracked upstream, use headBranchName as base_ref; if it's unpushed, fall back to default_branch and emit a stream.progress notice so the user knows which branch the agent will start from.

2. Auth cancellation was only normalized in chatParticipantImpl. The Copilot CLI /delegate handlers in copilotCLIChatSessions.ts:1469 and copilotCLIChatSessionsContribution.ts:1748 call cloudSessionProvider.delegate(...) directly, so they bypassed the pre-auth block I added. When the permissive session was missing there, users got a generic "invalid response" error from deep inside postCopilotAgentJob. Moved the permissive-session acquisition + CancellationError normalization into delegate() itself so all callers benefit.

3. Uncommitted-changes warning. Main path warned on neither; the CLI paths only checked indexChanges (staged). delegate() now warns uniformly on both staged and unstaged changes. Also fixed the pre-existing CLI checks in copilotCLIChatSessions.ts:1463 and copilotCLIChatSessionsContribution.ts:1742 to include workingTree.

4. Silent base-branch fallback. When we fall back to default_branch, we now emit "Current branch 'foo' is not on the remote; cloud agent will start from 'main'" so the user isn't surprised.

Judgment calls I deliberately didn't revert:

  • Chat-history summarization stays removed. Luke's Slack note that the agent is "inventing its own prompt different from what I asked it" is consistent with the local history summary overriding the user's actual intent. Codex, Claude, and Mission Control don't do client-side history summarization. If we find the agent loses useful context in practice, the right fix is server-side summarization in Mission Control (the Tasks API migration), not resurrecting the local summary.
  • copilotcloud.chat.confirmationCancelled telemetry is gone with the modal. The event had no meaning without the modal.
  • New tests for the changed paths — the rubber-duck noted the existing spec doesn't exercise delegation/auth/base-ref behavior. Agree, but adding that coverage is scope creep for this PR; happy to add in a follow-up.

474 tests still pass, tsc + eslint clean.

@alexdima

Copy link
Copy Markdown
Member

Thanks for the contribution! This repository has been archived because the project has moved into the main VS Code repository.

Could you please reopen/recreate this PR against:
https://github.com/microsoft/vscode/tree/main/extensions/copilot

We’ll continue reviewing contributions there. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants