* feat: AI translations at release, 7 new languages and regional fallback

* fix: proper plural forms for count strings and full translations

* fix: plural forms for selected, hidden and active counts

* fix: synced plugin updates failing on desktop and server-only plugins shown as stopped

* fix: restore rounded corners on app icons and favicon

* feat: desktop app auto updates with automatic, ask first or never modes

* feat: community and custom plugin registries

* feat: plugin guide for AI tools in the SDK with a lint check

* fix: keep the terminal WebSocket open past nginx's 60s default (#1588)

* chore: update changelog

* feat: show plugin download counts only and stop CI builds inflating them

* feat: hide installed plugins in the browse tab by default

* chore: pin bundled plugins to latest releases

* chore: update translations

* fix: focusing a saved password field and saving no longer erases it

* fix: tell desktop users when their 2.8 data needs 2.9 first

* fix: point windows update info at the x64 installer

* fix: keep desktop client tunnel sockets alive behind proxies

* chore: pin bundled plugins to latest releases

* fix: cover client tunnel keepalive in the c2s runtime tests

* fix: typing after opening a host no longer goes to the host search

* fix: accept tab and = separators in SSH config import (#1598)

* chore: update changelog

* chore: drop plugin versions from core changelog

* chore: update translations

* chore: lint, format, and bump version to 26.10.1

---------

Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com>
Co-authored-by: Iain <Iain1970@protonmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 11:35:48 -05:00
1 parent 6873bc92bb
commit 62ac931468
174 files changed
+94334 -39995

No files matched your search

+77 -4
View File
@@ -221,6 +221,8 @@ export interface PluginSummary {
enabled: boolean;
/** enabled | disabled | blocked | failed, or the loader's live state. */
state: string;
/** A linked desktop leaves this plugin to its server. */
runsOnServer?: boolean;
contributes: PluginContributions | null;
/** Lucide icon name from the manifest. */
icon?: string;
@@ -289,8 +291,14 @@ export interface RegistryPluginVersion {
size: number;
}
export type RegistryTier = "official" | "community" | "custom";
export interface RegistryPluginEntry {
id: string;
/** Which registry this entry comes from. */
registryId?: string;
registryName?: string;
tier?: RegistryTier;
name: string;
description: string;
author: string;
@@ -313,9 +321,18 @@ export interface RegistryPluginEntry {
pinnedVersion: string | null;
autoUpdate: boolean;
bundled: boolean;
/** Active installs when known, release downloads otherwise. */
installCount: number | null;
installCountSource: string | null;
/** How often the release files were downloaded, from the registry. */
downloads: number | null;
}
export interface RegistryListingSource {
id: string;
name: string;
tier: RegistryTier;
url: string;
lastCheckedAt: string | null;
error: string | null;
plugins: number;
}
export interface RegistryListing {
@@ -325,6 +342,7 @@ export interface RegistryListing {
lastCheckedAt: string | null;
error: string | null;
};
registries?: RegistryListingSource[];
managedByServer: boolean;
plugins: RegistryPluginEntry[];
}
@@ -373,7 +391,7 @@ export async function getPluginRegistry(
/** `capabilities` is exactly what the consent prompt showed. */
export async function installPlugin(
pluginId: string,
options: { version?: string; capabilities: string[] },
options: { version?: string; capabilities: string[]; registryId?: string },
): Promise<{ id: string; version: string; state: string }> {
try {
const response = await rbacApi.post(
@@ -405,6 +423,61 @@ export async function updatePlugin(
}
}
export interface CustomRegistryInfo {
id: string;
name: string;
url: string;
keyId: string;
enabled: boolean;
addedAt: string;
}
export interface RegistriesState {
managedByServer: boolean;
official: { id: string; url: string };
community: { id: string; url: string; enabled: boolean };
custom: CustomRegistryInfo[];
}
export interface RegistryPreview {
id: string;
name: string;
url: string;
keyId: string;
plugins: number;
}
export async function getRegistries(): Promise<RegistriesState> {
const response = await rbacApi.get("/plugins/registries");
return response.data;
}
export async function previewRegistry(url: string): Promise<RegistryPreview> {
const response = await rbacApi.post("/plugins/registries/preview", { url });
return response.data;
}
export async function addRegistry(
url: string,
keyId: string,
): Promise<CustomRegistryInfo> {
const response = await rbacApi.post("/plugins/registries", { url, keyId });
return response.data;
}
export async function setRegistryEnabled(
id: string,
enabled: boolean,
): Promise<void> {
await rbacApi.patch(`/plugins/registries/${encodeURIComponent(id)}`, {
enabled,
});
}
export async function removeRegistry(id: string): Promise<void> {
await rbacApi.delete(`/plugins/registries/${encodeURIComponent(id)}`);
}
export async function updateAllPlugins(): Promise<UpdateAllResult> {
try {
const response = await rbacApi.post("/plugins/update-all");