fix(plugins): pin every command to its activation runtime

This commit is contained in:
陈奇 committed 2026-10-26 09:59:34 +00:00
1 parent 0a692dfc72
commit 8e4dd0c052
3 files changed
+29 -2

No files matched your search

+1 -1
View File
@@ -351,7 +351,7 @@ class PluginContributionService {
command: commandId,
...(args === undefined ? {} : { args }),
invocation: freezeJson({ source: invocation.source ?? "host", context: invocation.context ?? {} }),
}, contribution.interactive ? { timeoutMs: InteractiveCommandLimits.const.timeoutMs, expectedIdentity: identity } : undefined);
}, { expectedIdentity: identity, ...(contribution.interactive ? { timeoutMs: InteractiveCommandLimits.const.timeoutMs } : {}) });
failed = false;
return result;
} finally {
@@ -117,7 +117,8 @@ test("only explicitly declared interactive commands receive the fixed budget and
});
await service.executeCommand(`${pluginManifest.id}.hello`);
await service.executeCommand(`${pluginManifest.id}.interactive`);
assert.equal(optionsSeen[0], undefined, "ordinary commands retain the existing router deadline");
assert.equal(Object.hasOwn(optionsSeen[0], "timeoutMs"), false, "ordinary commands retain the existing router deadline");
assert.equal(optionsSeen[0].expectedIdentity.runtimeId, runtimeId);
assert.equal(optionsSeen[1].timeoutMs, 180_000);
assert.equal(optionsSeen[1].expectedIdentity.runtimeId, runtimeId);
assert.deepEqual(finished, [runtimeId, runtimeId]);
@@ -12,6 +12,7 @@ const { createPluginPaths } = require("./paths.cjs");
const { RPC_ERRORS } = require("./rpcRouter.cjs");
const { RuntimeSupervisor } = require("./runtimeSupervisor.cjs");
const { createContainmentError } = require("./utilityPluginRuntime.cjs");
const { PluginContributionService } = require("./contributionService.cjs");
function pluginManifest(overrides = {}) {
return {
@@ -66,6 +67,31 @@ function createFixture(context, runtimeFactory, supervisorOptions = {}) {
return { database, manifest, runtimeOptions, supervisor };
}
test("ordinary command activation A cannot dispatch on replacement runtime B", async (t) => {
let executed = 0, activation;
const fixture = createFixture(t, () => ({ async start(config) { return { pluginId: config.pluginId, pluginVersion: config.pluginVersion,
apiVersion: config.apiVersion, enabledFeatures: config.enabledFeatures }; }, async stop() {}, async request() { executed++; return null; } }));
t.after(() => fixture.supervisor.shutdown());
const manifest = { ...fixture.manifest, contributes: { commands: [{ id: `${fixture.manifest.id}.ordinary`, title: "Ordinary" }] } };
const finished = [];
const service = new PluginContributionService({
database: { listPlugins: () => [{ id: manifest.id, manifest, activeVersion: manifest.version, enabled: true, runtime: {} }] },
onCommandFinished: (runtimeId) => finished.push(runtimeId),
runtimeSupervisor: {
async start(pluginId) {
activation = await fixture.supervisor.start(pluginId);
await fixture.supervisor.restart(pluginId);
return activation;
},
request: (...args) => fixture.supervisor.request(...args),
},
});
await assert.rejects(service.executeCommand(`${manifest.id}.ordinary`), /no longer matches the authorized activation/);
assert.notEqual(fixture.supervisor.getRuntimeIdentity(manifest.id).runtimeId, activation.runtimeId);
assert.equal(executed, 0, "replacement B receives no stale command");
assert.deepEqual(finished, [activation.runtimeId]);
});
test("supervisor prefers the ordinary browser runtime and enforces negotiated identity", async (context) => {
const calls = [];
const initialEnvironment = { locale: "en", theme: "light", reducedMotion: false, highContrast: false };