Files
vscode/src/vs/workbench/contrib/chat/browser/pluginAutoUpdate.ts
T
Sandeep Somavarapu da03e4ef04 extensions: add delayed auto-update mode to extensions.autoUpdate (#318974)
* extensions: add delayed auto-update mode to extensions.autoUpdate

Folds a 6-hour delayed auto-update mode into the existing
`extensions.autoUpdate` setting, which is now a string enum
('on' | 'delayed' | 'off') and policy-controllable via the
ExtensionsAutoUpdate policy.

When set to 'delayed', an extension whose newer version was published
less than 6 hours ago is treated as outdated-but-delayed: it is excluded
from auto-update and the activity badge, and surfaces an info status
explaining when it will be auto updated. Manual updates still work.

Includes startup migration from legacy values (true/'all'/
'onlyEnabledExtensions' -> 'on'; false/'none'/'onlySelectedExtensions'
-> 'off') plus defensive read-time normalization, and unit tests for the
new logic.

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

* update policy file

* extensions: address code review feedback for delayed auto-update

- Type IExtensionsConfiguration.autoUpdate as AutoUpdateConfigurationValue
- Keep ExtensionStatusAction.update() returning void; add recomputeStatus()
  for callers that need to await the computed status
- Preserve the higher-priority warning CSS class when the delayed info status
  is also applied to an outdated extension
- Hover awaits recomputeStatus() instead of update()
- Update pluginAutoUpdate doc comment to the on/delayed/off enum values

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

* update policies data

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 09:37:38 -07:00

81 lines
3.5 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../base/common/cancellation.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { autorun } from '../../../../base/common/observable.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IWorkbenchContribution } from '../../../common/contributions.js';
import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js';
import { IPluginInstallService } from '../common/plugins/pluginInstallService.js';
import { IPluginMarketplaceService } from '../common/plugins/pluginMarketplaceService.js';
/**
* Bridges the periodic plugin update *check* performed by
* {@link IPluginMarketplaceService} with the plugin update *action* exposed
* by {@link IPluginInstallService}.
*
* The marketplace service flips `hasUpdatesAvailable` to `true` roughly once
* a day when at least one cloned plugin repository has upstream changes.
* Without this contribution, that signal was never consumed and plugins
* were never auto-updated (see microsoft/vscode#308563).
*
* When the signal becomes `true` and `extensions.autoUpdate` is set to update
* extensions (`'on'` or `'delayed'`), we silently update all installed plugins.
* The `'off'` mode gates updates on a per-extension opt-in that has no plugin
* equivalent, so it is treated the same as disabled for plugins.
*
* The flag is cleared after every attempt — including failures — so the
* next periodic check's `false → true` transition can always re-trigger the
* autorun. `updateAllPlugins` already clears it on success; clearing again
* in `finally` is a no-op on the success path and handles the partial-
* failure path where the install service leaves the flag at `true`.
*/
export class PluginAutoUpdate extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.pluginAutoUpdate';
private _updateInFlight = false;
constructor(
@IPluginMarketplaceService private readonly _pluginMarketplaceService: IPluginMarketplaceService,
@IPluginInstallService private readonly _pluginInstallService: IPluginInstallService,
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
@ILogService private readonly _logService: ILogService,
) {
super();
this._register(autorun(reader => {
if (!this._pluginMarketplaceService.hasUpdatesAvailable.read(reader)) {
return;
}
void this._triggerAutoUpdate();
}));
}
private async _triggerAutoUpdate(): Promise<void> {
if (this._updateInFlight) {
return;
}
const autoUpdate = this._extensionsWorkbenchService.getAutoUpdateValue();
if (autoUpdate === 'off') {
return;
}
this._updateInFlight = true;
try {
await this._pluginInstallService.updateAllPlugins({ silent: true }, CancellationToken.None);
} catch (err) {
this._logService.error('[PluginAutoUpdate] Failed to auto-update plugins:', err);
} finally {
this._updateInFlight = false;
// Ensure the flag is cleared even on partial failure so the next
// periodic check can re-arm the autorun via a `false → true`
// transition.
this._pluginMarketplaceService.clearUpdatesAvailable();
}
}
}