diff --git a/src/browser/features/Messages/QueuedMessage.tsx b/src/browser/features/Messages/QueuedMessage.tsx index 9a11a0012e..bc2adc3d9a 100644 --- a/src/browser/features/Messages/QueuedMessage.tsx +++ b/src/browser/features/Messages/QueuedMessage.tsx @@ -16,7 +16,7 @@ interface QueuedPreview { fallbackLabel: string; } -export function deriveQueuedPreview(message: QueuedMessageType): QueuedPreview { +function deriveQueuedPreview(message: QueuedMessageType): QueuedPreview { const hasReviews = (message.reviews?.length ?? 0) > 0; const sanitizedText = hasReviews ? message.content.replace(/[\s\S]*?<\/review>\s*/g, "").trim() diff --git a/src/browser/features/Settings/Sections/GeneralSection.tsx b/src/browser/features/Settings/Sections/GeneralSection.tsx index a908375ffb..05c97b298e 100644 --- a/src/browser/features/Settings/Sections/GeneralSection.tsx +++ b/src/browser/features/Settings/Sections/GeneralSection.tsx @@ -30,8 +30,8 @@ import { isGenericFontFamily, } from "@/browser/terminal/terminalFontFamily"; import { - CODER_ARCHIVE_BEHAVIORS, DEFAULT_CODER_ARCHIVE_BEHAVIOR, + isCoderWorkspaceArchiveBehavior, type CoderWorkspaceArchiveBehavior, } from "@/common/config/coderArchiveBehavior"; @@ -141,13 +141,6 @@ const ARCHIVE_BEHAVIOR_OPTIONS = [ { value: "delete", label: "Delete workspace" }, ] as const; -function isCoderWorkspaceArchiveBehavior(value: unknown): value is CoderWorkspaceArchiveBehavior { - return ( - typeof value === "string" && - CODER_ARCHIVE_BEHAVIORS.includes(value as CoderWorkspaceArchiveBehavior) - ); -} - // Browser mode: window.api is not set (only exists in Electron via preload) const isBrowserMode = typeof window !== "undefined" && !window.api; diff --git a/src/common/config/coderArchiveBehavior.ts b/src/common/config/coderArchiveBehavior.ts index 3fd5b6cf73..cddff00cc9 100644 --- a/src/common/config/coderArchiveBehavior.ts +++ b/src/common/config/coderArchiveBehavior.ts @@ -3,3 +3,12 @@ export const CODER_ARCHIVE_BEHAVIORS = ["keep", "stop", "delete"] as const; export type CoderWorkspaceArchiveBehavior = (typeof CODER_ARCHIVE_BEHAVIORS)[number]; export const DEFAULT_CODER_ARCHIVE_BEHAVIOR: CoderWorkspaceArchiveBehavior = "stop"; + +export function isCoderWorkspaceArchiveBehavior( + value: unknown +): value is CoderWorkspaceArchiveBehavior { + return ( + typeof value === "string" && + CODER_ARCHIVE_BEHAVIORS.includes(value as CoderWorkspaceArchiveBehavior) + ); +} diff --git a/src/common/constants/toolLimits.ts b/src/common/constants/toolLimits.ts index 510153a49c..b5a0e7e195 100644 --- a/src/common/constants/toolLimits.ts +++ b/src/common/constants/toolLimits.ts @@ -19,8 +19,6 @@ export const MAX_TODOS = 7; // Maximum number of TODO items in a list // Keep only the most recent lines (tail), drop older lines export const INIT_HOOK_MAX_LINES = 500; -export const STATUS_MESSAGE_MAX_LENGTH = 60; // Maximum length for status messages (auto-truncated) - // Web fetch tool limits export const WEB_FETCH_TIMEOUT_SECS = 15; // curl timeout export const WEB_FETCH_MAX_OUTPUT_BYTES = 64 * 1024; // 64KB markdown output diff --git a/src/common/types/global.d.ts b/src/common/types/global.d.ts index 1ab6ecf259..d9ca7540c6 100644 --- a/src/common/types/global.d.ts +++ b/src/common/types/global.d.ts @@ -2,12 +2,6 @@ import type { RouterClient } from "@orpc/server"; import type { AppRouter } from "@/node/orpc/router"; import type { MuxDeepLinkPayload } from "@/common/types/deepLink"; -// Our simplified permission modes for UI -export type UIPermissionMode = "plan" | "edit"; - -// Claude SDK permission modes -export type SDKPermissionMode = "default" | "acceptEdits" | "bypassPermissions" | "plan"; - declare global { interface WindowApi { platform: NodeJS.Platform; diff --git a/src/node/config.ts b/src/node/config.ts index 7c4a56c88f..8d736b7daf 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -37,8 +37,8 @@ import { isIncompatibleRuntimeConfig } from "@/common/utils/runtimeCompatibility import { getMuxHome } from "@/common/constants/paths"; import { GATEWAY_PROVIDERS } from "@/common/constants/providers"; import { - CODER_ARCHIVE_BEHAVIORS, DEFAULT_CODER_ARCHIVE_BEHAVIOR, + isCoderWorkspaceArchiveBehavior, type CoderWorkspaceArchiveBehavior, } from "@/common/config/coderArchiveBehavior"; import { PlatformPaths } from "@/common/utils/paths"; @@ -100,13 +100,7 @@ function parseUpdateChannel(value: unknown): UpdateChannel | undefined { function parseCoderWorkspaceArchiveBehavior( value: unknown ): CoderWorkspaceArchiveBehavior | undefined { - if (typeof value !== "string") { - return undefined; - } - - return CODER_ARCHIVE_BEHAVIORS.includes(value as CoderWorkspaceArchiveBehavior) - ? (value as CoderWorkspaceArchiveBehavior) - : undefined; + return isCoderWorkspaceArchiveBehavior(value) ? value : undefined; } function resolveCoderWorkspaceArchiveBehavior( diff --git a/src/node/runtime/hostGlobalMuxHome.ts b/src/node/runtime/hostGlobalMuxHome.ts index 495b4d0551..4c701d70f9 100644 --- a/src/node/runtime/hostGlobalMuxHome.ts +++ b/src/node/runtime/hostGlobalMuxHome.ts @@ -1,4 +1,5 @@ import type { Runtime } from "./Runtime"; +import { LocalRuntime } from "./LocalRuntime"; import { RemoteRuntime } from "./RemoteRuntime"; /** @@ -9,3 +10,14 @@ import { RemoteRuntime } from "./RemoteRuntime"; export function shouldUseHostGlobalMuxFallback(runtime: Runtime): boolean { return runtime instanceof RemoteRuntime && runtime.getMuxHome() === "~/.mux"; } + +/** + * Return the runtime to use for reading global roots (`~/.mux/skills/`, `~/.mux/agents/`). + * + * SSH/Coder-SSH runtimes whose global mux home is the canonical `~/.mux` resolve global + * reads from the host filesystem (via a `LocalRuntime`). Runtimes with their own mux home + * (e.g. Docker's `/var/mux`) keep global reads on the runtime/container. + */ +export function resolveGlobalRuntime(runtime: Runtime, workspacePath: string): Runtime { + return shouldUseHostGlobalMuxFallback(runtime) ? new LocalRuntime(workspacePath) : runtime; +} diff --git a/src/node/services/agentDefinitions/agentDefinitionsService.ts b/src/node/services/agentDefinitions/agentDefinitionsService.ts index 61fc759090..82182377fc 100644 --- a/src/node/services/agentDefinitions/agentDefinitionsService.ts +++ b/src/node/services/agentDefinitions/agentDefinitionsService.ts @@ -2,9 +2,8 @@ import * as fs from "node:fs/promises"; import * as path from "node:path"; import type { Runtime } from "@/node/runtime/Runtime"; -import { LocalRuntime } from "@/node/runtime/LocalRuntime"; import { RemoteRuntime } from "@/node/runtime/RemoteRuntime"; -import { shouldUseHostGlobalMuxFallback } from "@/node/runtime/hostGlobalMuxHome"; +import { resolveGlobalRuntime } from "@/node/runtime/hostGlobalMuxHome"; import { getErrorMessage } from "@/common/utils/errors"; import { execBuffered, readFileString } from "@/node/utils/runtime/helpers"; import { shellQuote } from "@/node/runtime/backgroundCommands"; @@ -166,13 +165,6 @@ interface AgentDefinitionScanCandidate { runtime: Runtime; } -function getGlobalAgentRuntime(runtime: Runtime, workspacePath: string): Runtime { - // Remote runtimes whose global mux home semantically aliases the host's ~/.mux (for example - // SSH/Coder SSH) should read global agents from the host filesystem. Runtimes with their own - // mux home (for example Docker's /var/mux) keep global agent reads on the runtime/container. - return shouldUseHostGlobalMuxFallback(runtime) ? new LocalRuntime(workspacePath) : runtime; -} - function buildDiscoveryScans( runtime: Runtime, workspacePath: string, @@ -182,7 +174,7 @@ function buildDiscoveryScans( { scope: "global", root: roots.globalRoot, - runtime: getGlobalAgentRuntime(runtime, workspacePath), + runtime: resolveGlobalRuntime(runtime, workspacePath), }, { scope: "project", root: roots.projectRoot, runtime }, ]; @@ -198,7 +190,7 @@ function buildReadCandidates( { scope: "global", root: roots.globalRoot, - runtime: getGlobalAgentRuntime(runtime, workspacePath), + runtime: resolveGlobalRuntime(runtime, workspacePath), }, ]; } diff --git a/src/node/services/agentSkills/agentSkillsService.ts b/src/node/services/agentSkills/agentSkillsService.ts index 38a0671c6d..db7fd08810 100644 --- a/src/node/services/agentSkills/agentSkillsService.ts +++ b/src/node/services/agentSkills/agentSkillsService.ts @@ -1,9 +1,8 @@ import * as fs from "node:fs/promises"; import type { Runtime } from "@/node/runtime/Runtime"; -import { LocalRuntime } from "@/node/runtime/LocalRuntime"; import { RemoteRuntime } from "@/node/runtime/RemoteRuntime"; -import { shouldUseHostGlobalMuxFallback } from "@/node/runtime/hostGlobalMuxHome"; +import { resolveGlobalRuntime } from "@/node/runtime/hostGlobalMuxHome"; import { shellQuote } from "@/node/runtime/backgroundCommands"; import { getErrorMessage } from "@/common/utils/errors"; import { execBuffered, readFileString } from "@/node/utils/runtime/helpers"; @@ -87,12 +86,7 @@ function buildScanCandidates( workspacePath: string, roots: AgentSkillsRoots ): AgentSkillScanCandidate[] { - // Remote runtimes whose global mux home semantically aliases the host's ~/.mux (for example - // SSH/Coder SSH) should read global skills from the host filesystem. Runtimes with their own - // mux home (for example Docker's /var/mux) keep global skill reads on the runtime/container. - const globalRuntime = shouldUseHostGlobalMuxFallback(runtime) - ? new LocalRuntime(workspacePath) - : runtime; + const globalRuntime = resolveGlobalRuntime(runtime, workspacePath); return buildScanOrder(roots).map((scan) => ({ ...scan, diff --git a/src/node/services/browser/AgentBrowserSessionDiscoveryService.ts b/src/node/services/browser/AgentBrowserSessionDiscoveryService.ts index 11c17250e4..c859595605 100644 --- a/src/node/services/browser/AgentBrowserSessionDiscoveryService.ts +++ b/src/node/services/browser/AgentBrowserSessionDiscoveryService.ts @@ -254,78 +254,14 @@ function extractSessionNames(payload: unknown): string[] { } async function listAgentBrowserSessionNames(env: NodeJS.ProcessEnv): Promise { - return await new Promise((resolve) => { - const childProcess = spawn("agent-browser", ["--json", "session", "list"], { - env, - stdio: ["ignore", "pipe", "pipe"], - windowsHide: true, - }); - const disposableProcess = new DisposableProcess(childProcess); - - let settled = false; - let stdout = ""; - let stderr = ""; - - const finish = (sessions: string[], error?: string): void => { - if (settled) { - return; - } - - settled = true; - clearTimeout(timeoutId); - if (error) { - log.debug("Agent-browser session discovery failed", { error }); - } - resolve(sessions); - }; - - const timeoutId = setTimeout(() => { - disposableProcess[Symbol.dispose](); - finish([], `agent-browser session list timed out after ${CLI_TIMEOUT_MS}ms`); - }, CLI_TIMEOUT_MS); - timeoutId.unref?.(); - - childProcess.stdout?.setEncoding("utf8"); - childProcess.stderr?.setEncoding("utf8"); - childProcess.stdout?.on("data", (chunk: string) => { - stdout += chunk; - }); - childProcess.stderr?.on("data", (chunk: string) => { - stderr += chunk; - }); - - childProcess.once("error", (error) => { - disposableProcess[Symbol.dispose](); - finish([], getErrorMessage(error)); - }); - - childProcess.once("close", (code, signal) => { - if (settled) { - return; - } - - if (code !== 0 || signal !== null) { - disposableProcess[Symbol.dispose](); - finish( - [], - stderr.trim() || `agent-browser session list exited with ${String(signal ?? code)}` - ); - return; - } - - let payload: unknown; - try { - payload = JSON.parse(stdout.trim()); - } catch (error) { - disposableProcess[Symbol.dispose](); - finish([], `agent-browser session list returned invalid JSON: ${getErrorMessage(error)}`); - return; - } - - disposableProcess[Symbol.dispose](); - finish(extractSessionNames(payload)); - }); - }); + // Reuse the generic CLI helper — the spawn+timeout+collect pattern was + // previously duplicated here before runAgentBrowserJsonCommand existed. + const payload = await runAgentBrowserJsonCommand( + env, + ["--json", "session", "list"], + "agent-browser session list" + ); + return payload != null ? extractSessionNames(payload) : []; } function parsePositiveInteger(value: unknown): number | null { diff --git a/src/node/utils/messages/toolResultAttachments.ts b/src/node/utils/messages/toolResultAttachments.ts index 21f5282ac9..fbb8b41d54 100644 --- a/src/node/utils/messages/toolResultAttachments.ts +++ b/src/node/utils/messages/toolResultAttachments.ts @@ -133,7 +133,7 @@ export function extractAttachmentsFromToolOutput( }; } -export type ProviderReadyToolAttachment = +type ProviderReadyToolAttachment = | { type: "attachment"; attachment: ExtractedToolAttachment } | { type: "text"; text: string }; @@ -197,7 +197,7 @@ export function createDataUrlForExtractedAttachment(attachment: ExtractedToolAtt return `data:${attachment.mediaType};base64,${attachment.data}`; } -export function createInlineSvgAttachmentText(attachment: ExtractedToolAttachment): string { +function createInlineSvgAttachmentText(attachment: ExtractedToolAttachment): string { if (attachment.mediaType !== SVG_MEDIA_TYPE) { throw new Error(`Expected an SVG attachment, got '${attachment.mediaType}'`); }