fix: hand the assistant's host flags to plugins in api.host.features

The sandbox runtime copied only the three policy flags from the host's
features, so a flag an assistant area adds (sidePanel, files, ...) never
reached api.host.features and the plugin hid the feature. The runtime
now copies every other flag the host names as true or false.

The host bridge assertions compare against withAssistant(...) like the
snapshot ones already did, because the init payload and host-change
carry the assistant's flags on top of the three policy flags.
This commit is contained in:
Linus Rath
2026-11-02 21:59:35 +01:00
parent e044299dea
commit dda31538af
2 changed files with 16 additions and 4 deletions
+11 -4
View File
@@ -85,21 +85,21 @@ describe('host bridge', () => {
const plugin = { id: 'p', name: 'P', version: '1.0.0', permissions: [], settings: {}, settingsSchema: {} } as never;
const instance = createBackgroundInstance({ plugin, tier: 'privileged', code: '', locale: 'en' });
const pending = (instance as unknown as { pendingInit: { hostFeatures?: unknown } | null }).pendingInit;
expect(pending?.hostFeatures).toEqual({ externalSenderLabel: true, externalRecipientWarning: false, noThirdPartyTraffic: false });
expect(pending?.hostFeatures).toEqual(withAssistant({ externalSenderLabel: true, externalRecipientWarning: false, noThirdPartyTraffic: false }));
// Before init has gone out a change lands in the payload ...
setExternalMail({});
expect(pending?.hostFeatures).toEqual({ externalSenderLabel: false, externalRecipientWarning: false, noThirdPartyTraffic: false });
expect(pending?.hostFeatures).toEqual(withAssistant({ externalSenderLabel: false, externalRecipientWarning: false, noThirdPartyTraffic: false }));
// ... after it, as a host-change message.
const sent: unknown[] = [];
(instance as unknown as { pendingInit: unknown }).pendingInit = null;
(instance as unknown as { send: (m: unknown) => void }).send = (m) => sent.push(m);
setExternalMail({ recipientWarning: true });
expect(sent).toEqual([{ type: 'host-change', features: { externalSenderLabel: false, externalRecipientWarning: true, noThirdPartyTraffic: false } }]);
expect(sent).toEqual([{ type: 'host-change', features: withAssistant({ externalSenderLabel: false, externalRecipientWarning: true, noThirdPartyTraffic: false }) }]);
// The admin turns third-party traffic off: running plugins hear it.
setNoThirdPartyTraffic(true);
expect(sent[1]).toEqual({ type: 'host-change', features: { externalSenderLabel: false, externalRecipientWarning: true, noThirdPartyTraffic: true } });
expect(sent[1]).toEqual({ type: 'host-change', features: withAssistant({ externalSenderLabel: false, externalRecipientWarning: true, noThirdPartyTraffic: true }) });
setNoThirdPartyTraffic(false);
instance.destroy();
}, 30_000);
@@ -165,5 +165,12 @@ describe('api.host in the sandbox runtime', () => {
expect(kept.externalSenderLabel).toBe(true);
expect((kept as { noThirdPartyTraffic?: boolean }).noThirdPartyTraffic).toBe(true);
expect(changes).toEqual([{ externalSenderLabel: true, externalRecipientWarning: false, noThirdPartyTraffic: true }]);
// The assistant's flags (lib/plugin-sandbox/assistant) reach the plugin too.
expect((kept as { sidePanel?: boolean }).sidePanel).toBeUndefined();
post(hostWindow, HOST_ORIGIN, { type: 'host-change', features: { externalSenderLabel: true, externalRecipientWarning: false, noThirdPartyTraffic: true, sidePanel: true } });
expect((kept as { sidePanel?: boolean }).sidePanel).toBe(true);
post(hostWindow, HOST_ORIGIN, { type: 'host-change', features: { externalSenderLabel: true, externalRecipientWarning: false, noThirdPartyTraffic: true, sidePanel: 'yes' } });
expect((kept as { sidePanel?: boolean }).sidePanel).toBe(false);
}, 30_000);
});
+5
View File
@@ -161,6 +161,11 @@ function applyHostFeatures(next: HostFeaturesView | undefined): void {
hostFeaturesState.externalRecipientWarning = next.externalRecipientWarning === true;
// A host older than 1.20 doesn't send it, and never let such a plugin run in the mode.
hostFeaturesState.noThirdPartyTraffic = next.noThirdPartyTraffic === true;
// The AI assistant's flags (lib/plugin-sandbox/assistant): whatever the host names, true or false.
const flags = hostFeaturesState as unknown as Record<string, boolean>;
for (const [key, value] of Object.entries(next)) {
if (key !== 'externalSenderLabel' && key !== 'externalRecipientWarning' && key !== 'noThirdPartyTraffic') flags[key] = value === true;
}
for (const listener of hostFeatureListeners) {
try { listener(hostFeaturesState); } catch (err) { console.error('[plugin] host feature listener failed', err); }
}