fix: prevent bundled OpenCode self-upgrades (#2525)
* fix: prevent bundled OpenCode self-upgrades * feat(vscode): support OpenCode upgrades * fix: refresh OpenCode update status on runtime switch --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
Bohdan Triapitsyn
parent
c0405d3fa4
commit
c88dd16d2a
@@ -89,7 +89,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
"group/toast toast !rounded-[var(--radius-xl)] !border-0 !px-3.5 !py-3 !gap-2.5 !text-foreground",
|
||||
"group/toast toast !rounded-[var(--radius-xl)] !border-0 !px-3.5 !py-3 !gap-2.5 !text-foreground [&_[data-cancel]+[data-button]]:!ml-2",
|
||||
title: "typography-ui-label !font-medium !text-foreground",
|
||||
description: "typography-meta !text-muted-foreground !mt-0.5",
|
||||
actionButton:
|
||||
|
||||
@@ -5,6 +5,7 @@ import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { runtimeFetch } from '@/lib/runtime-fetch';
|
||||
import { getRuntimeKey, subscribeRuntimeEndpointChanged } from '@/lib/runtime-switch';
|
||||
import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import { getDeferredSafeStorage } from '@/stores/utils/safeStorage';
|
||||
import {
|
||||
@@ -127,39 +128,50 @@ export const OpenCodeUpdateToast: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateAvailable = (event: Event) => {
|
||||
const version = resolveOpenCodeUpdateVersion((event as CustomEvent<unknown>).detail);
|
||||
showUpdateAvailableToast(version);
|
||||
};
|
||||
|
||||
let cancelled = false;
|
||||
const timeoutIds: Array<ReturnType<typeof setTimeout>> = [];
|
||||
|
||||
const checkForUpdate = async (attempt: number) => {
|
||||
const checkForUpdate = async (attempt: number, runtimeKey = getRuntimeKey()) => {
|
||||
try {
|
||||
const response = await runtimeFetch('/api/opencode/upgrade-status', { headers: { Accept: 'application/json' } });
|
||||
if (!response.ok) throw new Error(response.statusText || 'OpenCode upgrade status check failed');
|
||||
const status = await response.json().catch(() => null) as OpenCodeUpgradeStatusLike | null;
|
||||
const version = resolveOpenCodeUpgradeStatusVersion(status);
|
||||
if (!cancelled && version) {
|
||||
if (!cancelled && runtimeKey === getRuntimeKey() && version) {
|
||||
showUpdateAvailableToast(version);
|
||||
}
|
||||
} catch {
|
||||
const delay = CHECK_RETRY_DELAYS_MS[attempt];
|
||||
if (!cancelled && delay !== undefined) {
|
||||
timeoutIds.push(setTimeout(() => { void checkForUpdate(attempt + 1); }, delay));
|
||||
if (!cancelled && runtimeKey === getRuntimeKey() && delay !== undefined) {
|
||||
timeoutIds.push(setTimeout(() => { void checkForUpdate(attempt + 1, runtimeKey); }, delay));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdateAvailable = (event: Event) => {
|
||||
const version = resolveOpenCodeUpdateVersion((event as CustomEvent<unknown>).detail);
|
||||
if (version) {
|
||||
void checkForUpdate(0);
|
||||
}
|
||||
};
|
||||
|
||||
if (showOpenCodeUpdateNotifications) {
|
||||
timeoutIds.push(setTimeout(() => { void checkForUpdate(0); }, INITIAL_CHECK_DELAY_MS));
|
||||
}
|
||||
|
||||
const unsubscribeRuntime = subscribeRuntimeEndpointChanged(({ runtimeKey }) => {
|
||||
seenVersionsRef.current.clear();
|
||||
toast.dismiss(UPDATE_TOAST_ID);
|
||||
if (useUIStore.getState().showOpenCodeUpdateNotifications) {
|
||||
void checkForUpdate(0, runtimeKey);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('openchamber:opencode-update-available', onUpdateAvailable);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
for (const timeoutId of timeoutIds) clearTimeout(timeoutId);
|
||||
unsubscribeRuntime();
|
||||
window.removeEventListener('openchamber:opencode-update-available', onUpdateAvailable);
|
||||
};
|
||||
}, [runUpgrade, showOpenCodeUpdateNotifications, t]);
|
||||
|
||||
@@ -181,6 +181,7 @@ describe('resolveOpenCodeUpgradeStatusVersion', () => {
|
||||
resolveOpenCodeUpgradeStatusVersion({
|
||||
available: true,
|
||||
latestVersion: '1.16.0',
|
||||
upgrade: { supported: true },
|
||||
}),
|
||||
).toBe('1.16.0');
|
||||
});
|
||||
@@ -190,6 +191,7 @@ describe('resolveOpenCodeUpgradeStatusVersion', () => {
|
||||
resolveOpenCodeUpgradeStatusVersion({
|
||||
available: true,
|
||||
latestVersion: ' 1.16.0 ',
|
||||
upgrade: { supported: true },
|
||||
}),
|
||||
).toBe('1.16.0');
|
||||
});
|
||||
@@ -211,6 +213,22 @@ describe('resolveOpenCodeUpgradeStatusVersion', () => {
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('fails closed when the server does not explicitly support upgrades', () => {
|
||||
expect(
|
||||
resolveOpenCodeUpgradeStatusVersion({
|
||||
available: true,
|
||||
latestVersion: '1.16.0',
|
||||
}),
|
||||
).toBe('');
|
||||
expect(
|
||||
resolveOpenCodeUpgradeStatusVersion({
|
||||
available: true,
|
||||
latestVersion: '1.16.0',
|
||||
upgrade: { supported: false },
|
||||
}),
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('returns empty string when available is missing or null', () => {
|
||||
expect(
|
||||
resolveOpenCodeUpgradeStatusVersion({
|
||||
|
||||
@@ -79,6 +79,9 @@ export const resolveOpenCodeUpdateVersion = (detail: unknown): string => {
|
||||
export interface OpenCodeUpgradeStatusLike {
|
||||
readonly available?: boolean | null;
|
||||
readonly latestVersion?: string | null;
|
||||
readonly upgrade?: {
|
||||
readonly supported?: boolean | null;
|
||||
} | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,6 +93,7 @@ export const resolveOpenCodeUpgradeStatusVersion = (
|
||||
status: OpenCodeUpgradeStatusLike | null | undefined,
|
||||
): string => {
|
||||
if (!status) return '';
|
||||
if (status.upgrade?.supported !== true) return '';
|
||||
if (status.available !== true) return '';
|
||||
if (typeof status.latestVersion !== 'string') return '';
|
||||
return status.latestVersion.trim();
|
||||
|
||||
Reference in New Issue
Block a user