diff --git a/CHANGELOG.md b/CHANGELOG.md index f6099701e7bb..22ffe666981c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3110,6 +3110,7 @@ This audited record covers the complete v2026.5.28..v2026.5.31-beta.4 history: 4 - CLI/context engines: bootstrap and finalize non-legacy context engines for CLI turns while preserving transcript snapshots and deferred maintenance ownership. (#81869) Thanks @sahilsatralkar. - Telegram: persist polling updates through restart replay so queued same-topic messages resume in order instead of losing context after a gateway restart. (#82256) Thanks @VACInc. - Gateway/Gmail: abort in-flight Gmail watcher startup and hot-reload restarts before shutdown so reloads cannot spawn `gog serve` after the Gateway is closing. Thanks @frankekn. +- Plugins: reuse compatible current metadata snapshots during runtime load-context resolution, so plugin tool discovery does not discard gateway-provided manifests before auto-enable settles. - Agents/Codex: fall back to the embedded PI runner when OpenAI's implicit Codex harness preference cannot find a registered Codex plugin, preventing OpenAI-compatible gateway requests from failing with an unregistered harness error. Fixes #82437. - Agents/OpenAI: honor `openai-codex:*` entries placed ahead of API-key backups in `auth.order.openai` for explicit OpenAI PI runs, and accept `models auth login --provider openai-codex --device-code` for headless sign-in. Fixes #82521. (#82605) - CLI/channels: install missing externalized same-id channel plugins during `channels add --channel `, so recovery for WhatsApp and other externalized stock channels does not require a separate `plugins enable` step. Fixes #82533. diff --git a/src/plugins/runtime/load-context.test.ts b/src/plugins/runtime/load-context.test.ts index 604af3553157..1212d2a3c810 100644 --- a/src/plugins/runtime/load-context.test.ts +++ b/src/plugins/runtime/load-context.test.ts @@ -21,6 +21,7 @@ const metadataSnapshot = { workspaceDir: "/resolved-workspace", }; const loadPluginMetadataSnapshotMock = vi.fn(() => metadataSnapshot); +const isPluginMetadataSnapshotCompatibleMock = vi.fn(() => true); const getCurrentPluginMetadataSnapshotMock = vi.fn(() => undefined); const setCurrentPluginMetadataSnapshotMock = vi.fn(); const clearCurrentPluginMetadataSnapshotMock = vi.fn(); @@ -45,6 +46,7 @@ vi.mock("../../agents/agent-scope.js", () => ({ })); vi.mock("../plugin-metadata-snapshot.js", () => ({ + isPluginMetadataSnapshotCompatible: isPluginMetadataSnapshotCompatibleMock, loadPluginMetadataSnapshot: loadPluginMetadataSnapshotMock, resolvePluginMetadataSnapshot: loadPluginMetadataSnapshotMock, })); @@ -69,6 +71,8 @@ describe("resolvePluginRuntimeLoadContext", () => { applyPluginAutoEnableMock.mockReset(); getCurrentPluginMetadataSnapshotMock.mockReset(); getCurrentPluginMetadataSnapshotMock.mockReturnValue(undefined); + isPluginMetadataSnapshotCompatibleMock.mockReset(); + isPluginMetadataSnapshotCompatibleMock.mockReturnValue(true); loadPluginMetadataSnapshotMock.mockClear(); getCurrentPluginMetadataSnapshotMock.mockClear(); setCurrentPluginMetadataSnapshotMock.mockClear(); diff --git a/src/plugins/runtime/load-context.ts b/src/plugins/runtime/load-context.ts index 382e25a7e8ef..b42168c5c778 100644 --- a/src/plugins/runtime/load-context.ts +++ b/src/plugins/runtime/load-context.ts @@ -14,7 +14,10 @@ import { import { extractPluginInstallRecordsFromInstalledPluginIndex } from "../installed-plugin-index-install-records.js"; import type { PluginLoadOptions } from "../loader.js"; import type { PluginManifestRegistry } from "../manifest-registry.js"; -import { resolvePluginMetadataSnapshot } from "../plugin-metadata-snapshot.js"; +import { + isPluginMetadataSnapshotCompatible, + resolvePluginMetadataSnapshot, +} from "../plugin-metadata-snapshot.js"; import type { PluginLogger } from "../types.js"; const log = createSubsystemLogger("plugins"); @@ -73,18 +76,16 @@ export function resolvePluginRuntimeLoadContext( const rawConfig = options?.config ?? getRuntimeConfig(); const rawWorkspaceDir = options?.workspaceDir ?? resolveAgentWorkspaceDir(rawConfig, resolveDefaultAgentId(rawConfig)); - const metadataSnapshot = options?.manifestRegistry - ? undefined - : resolvePluginMetadataSnapshot({ - config: rawConfig, - env, - workspaceDir: rawWorkspaceDir, - allowWorkspaceScopedCurrent: true, - }); - const manifestRegistry = options?.manifestRegistry ?? metadataSnapshot?.manifestRegistry; - const installRecords = metadataSnapshot - ? extractPluginInstallRecordsFromInstalledPluginIndex(metadataSnapshot.index) - : undefined; + const initialMetadataSnapshot = + options?.manifestRegistry === undefined + ? resolvePluginMetadataSnapshot({ + config: rawConfig, + env, + workspaceDir: rawWorkspaceDir, + allowWorkspaceScopedCurrent: true, + }) + : undefined; + const manifestRegistry = options?.manifestRegistry ?? initialMetadataSnapshot?.manifestRegistry; const activationSourceConfig = resolvePluginActivationSourceConfig({ config: rawConfig, activationSourceConfig: options?.activationSourceConfig, @@ -93,11 +94,33 @@ export function resolvePluginRuntimeLoadContext( config: rawConfig, env, manifestRegistry, - discovery: metadataSnapshot?.discovery, + discovery: initialMetadataSnapshot?.discovery, }); const config = autoEnabled.config; const workspaceDir = options?.workspaceDir ?? resolveAgentWorkspaceDir(config, resolveDefaultAgentId(config)); + const metadataSnapshot = + options?.manifestRegistry !== undefined + ? undefined + : initialMetadataSnapshot && + isPluginMetadataSnapshotCompatible({ + snapshot: initialMetadataSnapshot, + config, + env, + workspaceDir, + }) + ? initialMetadataSnapshot + : resolvePluginMetadataSnapshot({ + config, + env, + workspaceDir, + allowWorkspaceScopedCurrent: true, + ...(initialMetadataSnapshot ? { index: initialMetadataSnapshot.index } : {}), + }); + const finalManifestRegistry = options?.manifestRegistry ?? metadataSnapshot?.manifestRegistry; + const installRecords = metadataSnapshot + ? extractPluginInstallRecordsFromInstalledPluginIndex(metadataSnapshot.index) + : undefined; if (metadataSnapshot) { // Reusable snapshots stay available to later manifest-policy lookups for this runtime load. if (isReusableCurrentPluginMetadataSnapshot(metadataSnapshot)) { @@ -119,7 +142,7 @@ export function resolvePluginRuntimeLoadContext( workspaceDir, env, logger: options?.logger ?? createPluginRuntimeLoaderLogger(), - manifestRegistry, + ...(finalManifestRegistry ? { manifestRegistry: finalManifestRegistry } : {}), installRecords, }; }