Skip to content
Merged
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
30 changes: 23 additions & 7 deletions src/renderer/components/providers/claude/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ registerConflictResolverDefaults("claude", {

registerComposerControls("claude", ({ capabilities, config, isDisabled, onConfigChange }) => {
const isPlanMode = (config.mode ?? "agent") !== "agent";

// Auto mode is only supported for Sonnet 4.6+, Opus 4.6, and Opus 4.7.
// Filter it out for Haiku and other models that don't support it.
const AUTO_CAPABLE_MODELS = new Set(["sonnet", "claude-opus-4-6", "claude-opus-4-7"]);
const modelSupportsAuto = !config.model || AUTO_CAPABLE_MODELS.has(config.model);
const filteredPolicies = modelSupportsAuto
? capabilities.approvalPolicies
: capabilities.approvalPolicies.filter((p) => p.id !== "auto");

const currentPolicy =
config.approvalPolicy ??
capabilities.bypassPermissions?.approvalPolicy ??
capabilities.approvalPolicies[0]?.id ??
"default";
// If the current policy is not available for this model, fall back to
// bypassPermissions since auto mode was the reason it was filtered.
const effectivePolicy = filteredPolicies.some((p) => p.id === currentPolicy)
? currentPolicy
: "bypassPermissions";

return [
...(capabilities.modes.length === 2
? [
Expand All @@ -44,17 +64,13 @@ registerComposerControls("claude", ({ capabilities, config, isDisabled, onConfig
}),
]
: []),
...(capabilities.approvalPolicies.length > 0
...(filteredPolicies.length > 0
? [
{
iconKind: "permission" as const,
options: capabilities.approvalPolicies,
options: filteredPolicies,
hideLabelOnWrap: true,
value:
config.approvalPolicy ??
capabilities.bypassPermissions?.approvalPolicy ??
capabilities.approvalPolicies[0]?.id ??
"default",
value: effectivePolicy,
isDisabled,
onChange: (value: string) => onConfigChange({ approvalPolicy: value }),
},
Expand Down
5 changes: 1 addition & 4 deletions src/renderer/components/thread/ThreadComposerSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,14 +163,11 @@ function buildControls(
? nextEfforts.includes(effectiveConfig.effort)
: true;
const nextContextIds = filteredCaps.modelContextSizes?.[model];
const contextValid =
!effectiveConfig.contextSize ||
(nextContextIds ? nextContextIds.includes(effectiveConfig.contextSize) : false);
const nextContextDefault = nextContextIds?.[0] ?? filteredCaps.defaultContextSize;
onPatch({
model,
...(!effortValid && nextEfforts.length > 0 ? { effort: nextEfforts[0] } : {}),
...(!contextValid && nextContextDefault ? { contextSize: nextContextDefault } : {}),
...(nextContextDefault ? { contextSize: nextContextDefault } : {}),
...(filteredCaps.fastModels?.includes(model) ? {} : { fast: false }),
...(filteredCaps.thinkingModels?.includes(model) ? {} : { thinking: false }),
});
Expand Down
17 changes: 15 additions & 2 deletions src/supervisor/agents/binaryResolver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { statSync } from "node:fs";
import type { ProjectLocation } from "@/shared/contracts";
import { getCachedExecutablePath, resolveExecutablePath, resolveWslExecutablePath } from "./base";

Expand Down Expand Up @@ -39,8 +40,20 @@ export function resolveAgentBinaryPath(
return resolved;
}
// posix: piggy-back on the shared exec-path cache populated by
// primeExecutablePathCache during agent detection.
return getCachedExecutablePath(binary);
// primeExecutablePathCache during agent detection. The cached path may come
// from a temporary login shell (e.g. fnm multishell) that has since been
// cleaned up — verify the file still exists so node-pty doesn't get a stale
// absolute path and fail with opaque "posix_spawnp failed".
const cached = getCachedExecutablePath(binary);
if (cached) {
try {
const s = statSync(cached);
if (!s.isFile() || (s.mode & 0o111) === 0) return undefined;
} catch {
return undefined;
}
}
return cached;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/supervisor/agents/claude/detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const claudeCapabilities: AgentCapability = {
modelEfforts: {
"claude-opus-4-6": ["low", "medium", "high", "max"],
haiku: [],
sonnet: ["low", "medium", "high"],
sonnet: ["low", "medium", "high", "max"],
},
contextSizes: [
{ id: "200k", label: "200k" },
Expand Down
2 changes: 1 addition & 1 deletion src/supervisor/agents/claude/probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const BUILTIN_MODELS: AgentCapability["models"] = [
const BUILTIN_MODEL_EFFORTS: AgentCapability["modelEfforts"] = {
"claude-opus-4-6": ["low", "medium", "high", "max"],
haiku: [],
sonnet: ["low", "medium", "high"],
sonnet: ["low", "medium", "high", "max"],
};

function parseSemverTriplet(version: string): [number, number, number] | null {
Expand Down
2 changes: 1 addition & 1 deletion src/supervisor/runtime/threadSessionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,7 @@ export class ThreadSessionManager {
let pty;
if (command) {
const ptyEnv = {
...process.env,
...sanitizedProcessEnv,
...(command.env ?? {}),
...agentEnv,
...terminalEnv,
Expand Down