mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-09 19:45:17 +02:00
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:
parent
a845eb14a6
commit
f0079b4db8
9 changed files with 7 additions and 132 deletions
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue