Add Discard changes button to the Agent Changes view#349
Add Discard changes button to the Agent Changes view#349anderson-joyle wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an offline “Discard changes” command to the Agent Changes view, enabling users to revert an agent workspace’s local edits back to the last-synced baseline using the LSP cached-file API.
Changes:
- Adds a new title-bar command (
microsoft-copilot-studio.discardChanges) and menu contribution for the Agent Changes view. - Implements discard logic in a dependency-injected module (
discardLocalChanges) plus command wiring (confirmation, progress, busy state, refresh). - Adds host tests covering restorable vs non-restorable changes and batch behavior; adds a helper to recompute local changes after discard.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/vscode-extensions/microsoft-powerplatformlang-extension/package.json | Adds the Discard Changes command + Agent Changes title-bar button contribution. |
| src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/tests/host/discardChanges.test.ts | New host tests for restorable checks and discard batch behavior. |
| src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/sync/workspaceScm.ts | Adds refreshLocalChanges() to refresh the tree/badge after discard. |
| src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/sync/discardChanges.ts | New pure discard/revert logic with restorable filtering and skip reporting. |
| src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/extension.ts | Registers the new discard command during activation. |
| src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/commands/discardChanges.ts | New command wiring: workspace selection, confirmation, LSP cache reads, file writes/deletes, and result reporting. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/commands/discardChanges.ts:116
DiscardDependencies.writeFileis documented as creating parent directories as needed, but the concrete implementation here callsworkspace.fs.writeFiledirectly. If the user deleted a directory along with a file (or a deleted file lives under a now-missing folder), restore will fail and the change will be skipped unnecessarily. Create the parent directory before writing.
writeFile: async (uri: Uri, content: string) => {
await VSworkspace.fs.writeFile(uri, Buffer.from(content, 'utf8'));
},
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/commands/discardChanges.ts:56
- When no workspace is resolved,
logWarningis called withmessagepassed only via the telemetry data object. This means no warning is shown in the VS Code UI (logger only shows UI messages when themessageargument is provided), so the command silently does nothing from the user's perspective.
if (!selectedWorkspace) {
const message = getAllWorkspaces().length > 0
? `No workspace selected. ${DISCARD_OPERATION} operation cancelled.`
: `No workspace found for ${DISCARD_OPERATION} operation`;
logger.logWarning(TelemetryEventsKeys.SyncWorkspaceCancel, undefined, { message });
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/commands/discardChanges.ts:57
- When no workspace is resolved, the command logs with
messageonly in the telemetry data object (logWarning(..., undefined, { message })). Sinceloggeronly shows a VS Code UI notification when themessageparameter is provided, this results in no user-visible feedback. Also, this differs from the existing sync commands, which treat "no workspace found" as an error and "no workspace selected" as a cancel with a visible message.
if (!selectedWorkspace) {
const message = getAllWorkspaces().length > 0
? `No workspace selected. ${DISCARD_OPERATION} operation cancelled.`
: `No workspace found for ${DISCARD_OPERATION} operation`;
logger.logWarning(TelemetryEventsKeys.SyncWorkspaceCancel, undefined, { message });
return;
src/CopilotStudio.Sync/WorkspaceSynchronizer.cs:4256
DiscardSkippedChange.Pathis currently populated withPath.GetFileName(change.Uri), which drops the relative directory (e.g.,knowledge/files/foo.pdfbecomes justfoo.pdf). The VS Code command surfaces skipped items to the user so they can restore them with Get; without the relative path this can be ambiguous and harder to locate (and loses context for similarly-named files).
private static DiscardSkippedChange CreateDiscardSkippedChange(Change change, string reason) => new()
{
SchemaName = change.SchemaName,
Path = Path.GetFileName(change.Uri),
Reason = reason,
};
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/CopilotStudio.Sync/WorkspaceSynchronizer.cs:4256
CreateDiscardSkippedChangereportsPathasPath.GetFileName(change.Uri), which drops the relative directory. This makes the user-facing skipped-items list ambiguous (e.g., multipleworkflow.json/metadata.ymlfiles under different workflow folders) and harder to act on when the warning says the items can be restored with Get.
private static DiscardSkippedChange CreateDiscardSkippedChange(Change change, string reason) => new()
{
SchemaName = change.SchemaName,
Path = Path.GetFileName(change.Uri),
Reason = reason,
};
src/vscode-extensions/microsoft-powerplatformlang-extension/client/src/tests/host/discardChanges.test.ts:22
- The PR description lists a new pure module
client/src/sync/discardChanges.tsand "12 unit tests" in this host test file, but the branch only containsclient/src/commands/discardChanges.tsand this test file currently has 2 formatting/PII tests. Please either update the PR description to match the actual implementation/testing, or add the missing module/tests if they were intended.
import * as assert from 'node:assert';
import { describe, test } from 'node:test';
import { formatDiscardErrorMessage, formatDiscardResultMessage } from '../../commands/discardChanges';
describe('discardChanges: telemetry', () => {
test('agent names are marked as PII while remaining visible to the user', () => {
const message = formatDiscardResultMessage('Contoso Support', {
restored: 1,
deleted: 0,
skipped: [],
});
assert.match(message, /<pii>Contoso Support<\/pii>/);
});
test('non-Error rejections retain their message and PII protection', () => {
const message = formatDiscardErrorMessage('request rejected');
assert.match(message, /<pii>request rejected<\/pii>/);
});
});
Adds a "Discard changes" title-bar button (arrow-back icon) to the Agent Changes view that reverts all of the selected agent's local changes back to their last-synced baseline, entirely offline. Resolves #343. Behavior: - Enabled only when local changes exist and no sync is running (mcs.agentChangesView.hasLocalChanges && !mcs.isSyncing). - Prompts for the agent when more than one workspace is connected, then shows a modal confirmation with the local-change count. - Per change: locally added files are deleted; locally updated/deleted files are rewritten from the cached (getCachedFile) baseline. - Binary changes the text-only cache cannot restore (the agent icon and knowledge file attachments) are skipped and reported so the user can restore them with Get. The revert logic lives in a pure, dependency-injected module (sync/discardChanges.ts) with host tests; the command module wires the real language-server cache read and workspace filesystem, holds the sync busy state for the operation, and refreshes the Agent Changes tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 29babeb3-e4ad-4c53-a6e8-60b0a5dbbe38
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Move discard restoration into the Sync/LSP layer so shared manifests, workflow pairs, CLI connection references, and missing directories are handled from the cached projection without authenticated refresh work. Preserve unresolved changes on refresh failures and redact agent names in telemetry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35147f40-12f6-448f-81cf-915987e9414c
Run the discard integration test against a temporary workspace copy so it cannot mutate shared test output or race workspace compiler tests. Assert the restored topic outcome without depending on unrelated baseline projection change counts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35147f40-12f6-448f-81cf-915987e9414c
Show picker cancellation and missing-workspace failures in the VS Code UI, matching existing sync command behavior. Preserve useful text when a command rejects with a non-Error value while keeping telemetry PII redaction. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35147f40-12f6-448f-81cf-915987e9414c
e821ff2 to
0a51e2a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/CopilotStudio.Sync/WorkspaceSynchronizer.cs:4256
- DiscardLocalChanges reports skipped items with
Path = Path.GetFileName(change.Uri), which drops the relative directory (e.g.infrastructure/connections/foo.sync.yamlbecomesfoo.sync.yaml). This makes the user-facing message ambiguous and less actionable, especially when multiple files share a name across folders. Use the full relativechange.Uriinstead.
private static DiscardSkippedChange CreateDiscardSkippedChange(Change change, string reason) => new()
{
SchemaName = change.SchemaName,
Path = Path.GetFileName(change.Uri),
Reason = reason,
};
Summary
Adds a Discard changes button (arrow-back
$(discard)icon) to the Agent Changes view title bar. After a modal confirmation, it restores the selected agent's local workspace to its last-synced baseline through a local-only, projection-aware Sync/LSP operation.Resolves #343.
Behavior
mcs.agentChangesView.hasLocalChanges && !mcs.isSyncing..mcs/botdefinition.json.references.mcs.yml;workflow.jsonandmetadata.ymlfor deleted workflows;infrastructure/connections/*.sync.yamlfiles;Design
WorkspaceSynchronizer.DiscardLocalChangesowns cache restoration and projection-aware file handling in the shared Sync layer.powerplatformls/discardLocalChangesis a local-only LSP endpoint that reads the current workspace, computes changes, applies the discard, and returns any unresolved changes.Errorrejections retain useful text through the repository-standardString(error)fallback.Key files
src/CopilotStudio.Sync/WorkspaceSynchronizer.cssrc/CopilotStudio.Sync/IWorkspaceSynchronizer.cs,Models.cssrc/CopilotStudio.Sync.UnitTests/DiscardLocalChangesTests.cssrc/LanguageServers/PowerPlatformLS/Impl.PullAgent/DiscardLocalChangesHandler.csclient/src/commands/discardChanges.tsclient/src/sync/workspaceScm.tsclient/src/sync/discardChanges.tsTesting
Known limitation