chore(code-mode): remove dead acpx code paths and stale copy

Code mode now runs through the code_agent_run tool (owning the ACP client), so the
legacy acpx shell-out paths are dead. Remove them:

- core: envForCommand (acpx-only CLAUDE_CODE_EXECUTABLE injection) from
  executeCommand; getCodeModeCommandLabel (acpx run-status label).
- renderer: the acpx-detection "switch agent / auto-flip the code-mode chip" flow —
  App.tsx executeCommand detection, the permission-request onSwitchAgent button +
  badge, and the composer's code-mode-detected listener.
- copy: Settings -> Code Mode and the code-with-agents skill summary no longer
  mention acpx; tidy stale comments (claude-exec, command-executor).

No behavior change for code mode; the general executeCommand tool is unaffected.
This commit is contained in:
Gagancreates 2026-06-02 01:15:37 +05:30
parent a845eb14a6
commit f0079b4db8
9 changed files with 7 additions and 132 deletions

View file

@ -2186,19 +2186,6 @@ function App() {
status: 'running',
timestamp: Date.now(),
}])
// Detect acpx-driven coding-agent runs so the composer can retroactively
// flip code mode on with the right agent (when the user reached the skill
// via plain prompt rather than the explicit toggle).
if (llmEvent.toolName === 'executeCommand') {
const input = llmEvent.input as { command?: unknown } | undefined
const cmd = typeof input?.command === 'string' ? input.command : ''
const match = cmd.match(/\bacpx\b[\s\S]*?\b(claude|codex)\b/)
if (match) {
window.dispatchEvent(new CustomEvent('code-mode-detected', {
detail: { runId: event.runId, agent: match[1] as 'claude' | 'codex' },
}))
}
}
} else if (llmEvent.type === 'finish-step') {
const nextUsage = normalizeUsage(llmEvent.usage)
if (nextUsage) {
@ -5881,24 +5868,6 @@ function App() {
onApproveSession={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'session')}
onApproveAlways={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'always')}
onDeny={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'deny')}
onSwitchAgent={async (newAgent) => {
const runIdForSwitch = tab.runId
await handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'deny')
window.dispatchEvent(new CustomEvent('code-mode-detected', {
detail: { runId: runIdForSwitch, agent: newAgent },
}))
if (runIdForSwitch) {
try {
await window.ipc.invoke('runs:createMessage', {
runId: runIdForSwitch,
message: `Use ${newAgent === 'claude' ? 'Claude Code' : 'Codex'} instead — rerun the same task with the same prompt, just swap the agent binary to \`${newAgent}\`.`,
codeMode: newAgent,
})
} catch (err) {
console.error('Failed to send swap-agent follow-up', err)
}
}
}}
isProcessing={isActive && isProcessing}
response={response}
/>

View file

@ -1,6 +1,5 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@ -9,7 +8,7 @@ import {
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
import { AlertTriangleIcon, CheckIcon, ChevronDownIcon, RefreshCwIcon, Terminal, XIcon } from "lucide-react";
import { AlertTriangleIcon, CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
import { useState, type ComponentProps } from "react";
import { ToolCallPart } from "@x/shared/dist/message.js";
import { ToolPermissionMetadata } from "@x/shared/dist/runs.js";
@ -21,7 +20,6 @@ export type PermissionRequestProps = ComponentProps<"div"> & {
onApproveSession?: () => void;
onApproveAlways?: () => void;
onDeny?: () => void;
onSwitchAgent?: (newAgent: 'claude' | 'codex') => void;
isProcessing?: boolean;
response?: 'approve' | 'deny' | null;
permission?: z.infer<typeof ToolPermissionMetadata>;
@ -42,7 +40,6 @@ export const PermissionRequest = ({
onApproveSession,
onApproveAlways,
onDeny,
onSwitchAgent,
isProcessing = false,
response = null,
permission,
@ -56,17 +53,6 @@ export const PermissionRequest = ({
: null;
const filePermission = permission?.kind === "file" ? permission : null;
// Detect acpx coding-agent invocations so we can show the agent identity and
// offer a one-click swap-and-retry.
const acpxAgent: 'claude' | 'codex' | null = (() => {
if (!command) return null;
const match = command.match(/\bacpx\b[\s\S]*?\b(claude|codex)\b\s+exec\b/);
return match ? (match[1] as 'claude' | 'codex') : null;
})();
const otherAgent: 'claude' | 'codex' | null = acpxAgent === 'claude' ? 'codex' : acpxAgent === 'codex' ? 'claude' : null;
const agentDisplay = acpxAgent === 'claude' ? 'Claude Code' : acpxAgent === 'codex' ? 'Codex' : null;
const otherDisplay = otherAgent === 'claude' ? 'Claude Code' : otherAgent === 'codex' ? 'Codex' : null;
const isResponded = response !== null;
const isApproved = response === 'approve';
@ -104,15 +90,6 @@ export const PermissionRequest = ({
</h3>
<p className="text-sm text-muted-foreground mt-1">
{isResponded ? "Requested:" : "The agent wants to execute:"} <span className="font-mono font-medium">{toolCall.toolName}</span>
{agentDisplay && (
<Badge
variant="secondary"
className="ml-2 align-middle bg-secondary text-foreground"
>
<Terminal className="size-3 mr-1" />
{agentDisplay}
</Badge>
)}
</p>
</div>
{isResponded && (
@ -220,18 +197,6 @@ export const PermissionRequest = ({
<XIcon className="size-4" />
Deny
</Button>
{otherAgent && otherDisplay && onSwitchAgent && (
<Button
variant="secondary"
size="sm"
onClick={() => onSwitchAgent(otherAgent)}
disabled={isProcessing}
className="flex-1"
>
<RefreshCwIcon className="size-4" />
Use {otherDisplay} instead
</Button>
)}
</div>
)}
</div>

View file

@ -283,20 +283,6 @@ function ChatInputInner({
}
}, [codeModeFeatureEnabled, codeModeEnabled])
// Listen for coding-agent runs that were triggered without the explicit code-mode
// toggle. App.tsx dispatches this when it sees an acpx executeCommand fire. We
// flip the pill on with the detected agent so the UI reflects what's happening.
useEffect(() => {
const handler = (ev: Event) => {
const detail = (ev as CustomEvent<{ runId?: string; agent?: 'claude' | 'codex' }>).detail
if (!detail || !detail.agent) return
if (runId && detail.runId && detail.runId !== runId) return
setCodeModeEnabled(true)
setCodingAgent(detail.agent)
}
window.addEventListener('code-mode-detected', handler)
return () => window.removeEventListener('code-mode-detected', handler)
}, [runId])
// Cross-platform basename — handles both / and \ separators.
const basename = useCallback((p: string): string => {

View file

@ -1781,9 +1781,8 @@ function CodeModeSettings({ dialogOpen }: { dialogOpen: boolean }) {
<p>
<strong className="text-foreground">Code mode</strong> lets the assistant delegate coding tasks
to <strong className="text-foreground">Claude Code</strong> or <strong className="text-foreground">Codex</strong> running
on your machine. Pick the agent inline from the composer; the assistant calls it via
<code className="mx-1 rounded bg-muted px-1 py-0.5 text-[11px]">acpx</code>
and streams results back into chat.
on your machine. Pick the agent inline from the composer; the assistant runs it on-device
and streams its work tool calls, file diffs, and approvals back into chat.
</p>
<p>
Requires an active <strong className="text-foreground">Claude Code</strong> subscription or

View file

@ -521,41 +521,9 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = {
* For builtin tools, returns a static friendly name (e.g., "Reading file").
* Falls back to the raw tool name if no mapping exists.
*/
// Phrases shown while a code-mode task is running. They advance over time (5s
// each) to read as progress, then hold on the last one until the task finishes.
const CODE_MODE_RUNNING_LABELS = [
'Working on the task…',
'Inspecting the project…',
'Digging into the code…',
'Figuring it out…',
'Making the changes…',
'Wiring things up…',
'Putting it together…',
]
const CODE_MODE_LABEL_INTERVAL_MS = 5000
// Detect acpx coding-agent invocations (code mode) and produce a status-aware
// label, e.g. "Working on the task…" → "Completed the task".
export const getCodeModeCommandLabel = (tool: ToolCall): string | null => {
if (tool.name !== 'executeCommand') return null
const input = normalizeToolInput(tool.input) as Record<string, unknown> | undefined
const command = typeof input?.command === 'string' ? input.command : ''
const match = command.match(/\bacpx\b[\s\S]*?\b(claude|codex)\b\s+exec\b/)
if (!match) return null
if (tool.status === 'error') return `Couldn't complete the task`
if (tool.status === 'completed') return `Completed the task`
// Advance through the phrases from the tool's start, holding on the last.
const elapsed = Math.max(0, Date.now() - tool.timestamp)
const step = Math.floor(elapsed / CODE_MODE_LABEL_INTERVAL_MS)
const idx = Math.min(step, CODE_MODE_RUNNING_LABELS.length - 1)
return CODE_MODE_RUNNING_LABELS[idx]
}
export const getToolDisplayName = (tool: ToolCall): string => {
const browserLabel = getBrowserControlLabel(tool)
if (browserLabel) return browserLabel
const codeModeLabel = getCodeModeCommandLabel(tool)
if (codeModeLabel) return codeModeLabel
const composioData = getComposioActionCardData(tool)
if (composioData) return composioData.label
return TOOL_DISPLAY_NAMES[tool.name] || tool.name

View file

@ -99,7 +99,7 @@ const definitions: SkillDefinition[] = [
{
id: "code-with-agents",
title: "Code with Agents",
summary: "Write code, build projects, create scripts, or fix bugs by delegating to Claude Code or Codex via acpx.",
summary: "Write code, build projects, create scripts, or fix bugs by delegating to Claude Code or Codex.",
content: codeWithAgentsSkill,
},
{

View file

@ -15,7 +15,6 @@ import { executeAction as executeComposioAction, isConfigured as isComposioConfi
import { CURATED_TOOLKITS, CURATED_TOOLKIT_SLUGS } from "@x/shared/dist/composio.js";
import { BrowserControlInputSchema, type BrowserControlInput } from "@x/shared/dist/browser-control.js";
import { BackgroundTaskSchema, TriggersSchema } from "@x/shared/dist/background-task.js";
import { resolveClaudeExeOnWindows } from "../../code-mode/acp/claude-exec.js";
import type { CodeModeManager } from "../../code-mode/acp/manager.js";
import type { CodePermissionRegistry } from "../../code-mode/acp/permission-registry.js";
import { ICodeModeConfigRepo } from "../../code-mode/repo.js";
@ -94,14 +93,6 @@ const LLMPARSE_MIME_TYPES: Record<string, string> = {
'.tiff': 'image/tiff',
};
function envForCommand(command: string): NodeJS.ProcessEnv | undefined {
if (process.platform !== 'win32') return undefined;
if (!/\bacpx\b/.test(command)) return undefined;
if (process.env.CLAUDE_CODE_EXECUTABLE) return undefined;
const exe = resolveClaudeExeOnWindows();
if (!exe) return undefined;
return { ...process.env, CLAUDE_CODE_EXECUTABLE: exe };
}
export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
loadSkill: {
@ -763,14 +754,11 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
// };
// }
const envOverride = envForCommand(command);
// Use abortable version when we have a signal
if (ctx?.signal) {
const { promise, process: proc } = executeCommandAbortable(command, {
cwd: workingDir,
signal: ctx.signal,
env: envOverride,
onData: (chunk: string) => {
ctx.publish({
runId: ctx.runId,
@ -800,7 +788,7 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
}
// Fallback to original for backward compatibility
const result = await executeCommand(command, { cwd: workingDir, env: envOverride });
const result = await executeCommand(command, { cwd: workingDir });
return {
success: result.exitCode === 0,

View file

@ -80,7 +80,7 @@ export async function executeCommand(
cwd?: string;
timeout?: number; // timeout in milliseconds
maxBuffer?: number; // max buffer size in bytes
env?: NodeJS.ProcessEnv; // override environment (e.g. CLAUDE_CODE_EXECUTABLE for acpx)
env?: NodeJS.ProcessEnv; // override environment
}
): Promise<CommandResult> {
try {

View file

@ -5,7 +5,7 @@ import { commonInstallPaths } from '../status.js';
// Windows-only: Node refuses to spawn `.cmd` files without `shell: true` (EINVAL),
// and the Claude ACP adapter spawns its executable directly. So we pre-resolve
// claude's real `.exe` from the npm-shim layout. Also used by the legacy acpx path.
// claude's real `.exe` from the npm-shim layout. Used by resolveClaudeExecutable below.
export function resolveClaudeExeOnWindows(): string | undefined {
// Candidate dirs = everything on PATH, plus well-known npm/pnpm/volta global
// bin dirs. Electron's runtime PATH can omit these even when the user's shell