fix: finish 2.9.0 plugin moves and get every check green

This commit is contained in:
LukeGus committed 2026-09-24 23:35:18 -05:00
1 parent 8c1e02af89
commit 6489b62ece
335 files changed
+29878 -17749

No files matched your search

@@ -22,6 +22,7 @@ import {
import { ADMIN_KEYS } from "./settings.js";
import { commandHistory } from "./tables.js";
import { createTerminalSocket } from "./terminal-socket.js";
import { validateAdminSettings } from "./settings-validation.js";
function toInfo(session: TerminalSession): LiveSessionInfo {
return {
@@ -55,6 +56,7 @@ function optionalService<T extends object>(
export async function activate(ctx: PluginContext) {
const log = createTerminalLogger(ctx.log);
ctx.settings.onValidate("admin", validateAdminSettings);
const table = await ctx.db.define(commandHistory);
const history = createHistoryRepository(ctx.db, table);
@@ -0,0 +1,32 @@
import {
parseImageHostPath,
parseImageLocalDir,
} from "./images/image-storage-settings.js";
import { ADMIN_KEYS } from "./settings.js";
/**
* Save-time checks for the admin settings: the image storage paths must be
* absolute, since a relative one silently depends on the server's cwd.
*/
export function validateAdminSettings(
values: Record<string, unknown>,
): Record<string, string> {
const errors: Record<string, string> = {};
const localDir = values[ADMIN_KEYS.imageLocalDir];
if (
typeof localDir === "string" &&
localDir.trim() !== "" &&
parseImageLocalDir(localDir) === null
) {
errors[ADMIN_KEYS.imageLocalDir] = "Use an absolute path";
}
const hostPath = values[ADMIN_KEYS.imageHostPath];
if (
typeof hostPath === "string" &&
hostPath.trim() !== "" &&
parseImageHostPath(hostPath) === null
) {
errors[ADMIN_KEYS.imageHostPath] = "Use an absolute path";
}
return errors;
}
@@ -81,7 +81,6 @@ interface ConnectToHostData {
keepaliveCountMax?: number;
[key: string]: unknown;
};
enableSessionLogging?: boolean;
/** When true, ignore key material and force password auth (fallback path). */
passwordFallbackOnly?: boolean;
};
@@ -1432,7 +1431,6 @@ export function createTerminalSocket(deps: TerminalSocketDeps) {
socks5ProxyChain?: unknown;
portKnockSequence?: ConnectToHostData["hostConfig"]["portKnockSequence"];
terminalConfig?: ConnectToHostData["hostConfig"]["terminalConfig"];
enableSessionLogging?: boolean;
})
| null = null;
@@ -1776,10 +1774,8 @@ export function createTerminalSocket(deps: TerminalSocketDeps) {
const hostDisplayName = `${username}@${ip}:${port}`;
const tabInstanceId = hostConfig.instanceId;
const sessionLoggingEnabled =
resolvedHostData?.enableSessionLogging ??
hostConfig.enableSessionLogging ??
true;
// The recordings.writer provider decides per host and user.
const sessionLoggingEnabled = true;
currentSessionId = sessionManager.createSession(
userId,
id,
@@ -107,24 +107,23 @@ import {
getNextTerminalFontSize,
getTerminalFontZoomDirection,
hydrateLocalSharedHostAuth,
getUserPreferences,
parseCustomKeybindings,
findMatchingKeybinding,
SnippetVariablesDialog,
type CustomKeybinding,
useConnectionDefaults,
getCookie,
isElectron,
logActivity,
getHostPassword,
patchOpenTab,
setHostAutoTmux,
} from "@termix/plugin-sdk/ui";
import {
useTranslation,
invokeAction,
usePluginApi,
useSlotContributions,
getCustomKeybindings,
getClientPreference,
logActivity,
getHostPassword,
patchOpenTab,
setHostAutoTmux,
} from "@termix/plugin-sdk/frontend";
type HostKeyVerificationData = Omit<
@@ -412,8 +411,11 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
const totpTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const connectionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const activityLoggedRef = useRef(false);
const commandHistoryTrackingEnabled =
hostConfig.enableCommandHistory !== false;
const commandHistoryTrackingEnabled = hostSetting(
hostConfig,
"enableCommandHistory",
true,
);
const { trackInput, getCurrentCommand, updateCurrentCommand } =
useCommandTracker({
@@ -758,6 +760,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
[clearAutosuggestion, terminal, trackInput],
);
const slotApiRef = useRef<TerminalSlotApi | null>(null);
const slotApi = useMemo<TerminalSlotApi>(
() => ({
host,
@@ -791,6 +794,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
hostConfig.instanceId,
],
);
slotApiRef.current = slotApi;
const overlayProps = useMemo<TerminalOverlayProps>(
() => ({
@@ -1426,6 +1430,8 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
refresh: () => hardRefresh(),
getApplicationCursorKeysMode: () =>
terminal?.modes?.applicationCursorKeysMode ?? false,
// What the tab menu's share entry needs, read at call time.
getShareTarget: () => slotApiRef.current?.getShareTarget() ?? null,
openFileManager: () => {
if (webSocketRef.current?.readyState === WebSocket.OPEN) {
webSocketRef.current.send(JSON.stringify({ type: "get_cwd" }));
@@ -1438,7 +1444,7 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
);
function getCopyOnSelect() {
return getCookie("copyOnSelect") === "true";
return getClientPreference("copyOnSelect") === "true";
}
function attemptReconnection() {
@@ -2945,12 +2951,12 @@ const TerminalInner = forwardRef<TerminalHandle, SSHTerminalProps>(
useEffect(() => {
let cancelled = false;
const loadKeybindings = () => {
getUserPreferences()
.then((prefs) => {
getCustomKeybindings()
.then((bindings) => {
if (!cancelled) {
customKeybindingsRef.current = parseCustomKeybindings(
prefs.customKeybindings,
).filter((kb) => kb.enabled);
customKeybindingsRef.current = bindings.filter(
(kb) => kb.enabled,
);
}
})
.catch(() => {
@@ -43,8 +43,12 @@ import {
useIsMobile,
} from "@termix/plugin-sdk/ui";
import {
useHostActions,
useTabs,
useTranslation,
useSlotContributions,
type PluginHostRecord,
type ShellApi,
} from "@termix/plugin-sdk/frontend";
type SelectedToolbarDensity = ToolbarDensity;
@@ -120,6 +124,25 @@ export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
),
[allSlotContributions],
);
// Quick links to the other tools a plugin offers for this host (tmux
// monitor, docker, tunnels, host metrics), from their host actions.
const hostActions = useHostActions();
const tabs = useTabs();
const hostLinks = useMemo(() => {
const record = host as unknown as PluginHostRecord;
return hostActions.filter(
(action) =>
action.kind === "open" &&
!action.items &&
(action.run || action.tabType) &&
action.when(record),
);
}, [hostActions, host]);
const openHostLink = (action: (typeof hostLinks)[number]) => {
const record = host as unknown as PluginHostRecord;
if (action.run) action.run(record, tabs as unknown as ShellApi);
else if (action.tabType) tabs.openTab(record, action.tabType);
};
const [density, setDensity] =
useState<SelectedToolbarDensity>(readStoredDensity);
const [responsiveDensity, setResponsiveDensity] =
@@ -233,6 +256,7 @@ export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
isTmuxAttached,
actionsEnabled,
slotContributions,
hostLinks,
t,
]);
@@ -603,6 +627,13 @@ export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
{density !== "icon" && t(contribution.titleKey)}
</span>
))}
{actionsEnabled &&
hostLinks.map((action) => (
<span key={action.id} className={CONTROL}>
<action.icon className="size-4" />
{density !== "icon" && t(action.titleKey)}
</span>
))}
<span className={cn(CONTROL, "h-8 px-2")}>
<LayoutGrid className="size-4" />
<ChevronDown className="size-4" />
@@ -683,6 +714,24 @@ export const TerminalToolbar: React.FC<TerminalToolbarProps> = ({
/>
</>
)}
{actionsEnabled && hostLinks.length > 0 && (
<>
<div className={SEPARATOR} />
{hostLinks.map((action) => (
<button
key={action.id}
type="button"
className={CONTROL}
aria-label={t(action.titleKey)}
title={t(action.titleKey)}
onClick={() => openHostLink(action)}
>
<action.icon className="size-4" />
{effectiveDensity !== "icon" && t(action.titleKey)}
</button>
))}
</>
)}
<div className={SEPARATOR} />
{densitySelect}
<button
@@ -1,9 +1,9 @@
import { getClientPreference } from "@termix/plugin-sdk/frontend";
import type { Terminal } from "@xterm/xterm";
import { getCookie } from "@termix/plugin-sdk/ui";
import { isPhysicalShortcutKey } from "./terminal-key-event";
export function getUseRightClickCopyPaste(): boolean {
return getCookie("rightClickCopyPaste") !== "false";
return getClientPreference("rightClickCopyPaste") !== "false";
}
export interface TerminalClipboardActions {