Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/browser/features/Messages/QueuedMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(/<review>[\s\S]*?<\/review>\s*/g, "").trim()
Expand Down
2 changes: 0 additions & 2 deletions src/common/constants/toolLimits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 0 additions & 6 deletions src/common/types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/node/runtime/hostGlobalMuxHome.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Runtime } from "./Runtime";
import { LocalRuntime } from "./LocalRuntime";
import { RemoteRuntime } from "./RemoteRuntime";

/**
Expand All @@ -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;
}
14 changes: 3 additions & 11 deletions src/node/services/agentDefinitions/agentDefinitionsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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,
Expand All @@ -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 },
];
Expand All @@ -198,7 +190,7 @@ function buildReadCandidates(
{
scope: "global",
root: roots.globalRoot,
runtime: getGlobalAgentRuntime(runtime, workspacePath),
runtime: resolveGlobalRuntime(runtime, workspacePath),
},
];
}
Expand Down
10 changes: 2 additions & 8 deletions src/node/services/agentSkills/agentSkillsService.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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,
Expand Down
80 changes: 8 additions & 72 deletions src/node/services/browser/AgentBrowserSessionDiscoveryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,78 +254,14 @@ function extractSessionNames(payload: unknown): string[] {
}

async function listAgentBrowserSessionNames(env: NodeJS.ProcessEnv): Promise<string[]> {
return await new Promise<string[]>((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 {
Expand Down
Loading