mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-24 20:28:16 +02:00
feat: show agent and add swap-and-retry on acpx permission card
This commit is contained in:
parent
d5dff0554c
commit
fd5e9a8657
2 changed files with 53 additions and 1 deletions
|
|
@ -5738,6 +5738,24 @@ function App() {
|
||||||
onApproveSession={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'session')}
|
onApproveSession={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'session')}
|
||||||
onApproveAlways={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'always')}
|
onApproveAlways={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve', 'always')}
|
||||||
onDeny={() => handlePermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'deny')}
|
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}
|
isProcessing={isActive && isProcessing}
|
||||||
response={response}
|
response={response}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { AlertTriangleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, XCircleIcon, XIcon } from "lucide-react";
|
import { AlertTriangleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, RefreshCwIcon, Terminal, XCircleIcon, XIcon } from "lucide-react";
|
||||||
import type { ComponentProps } from "react";
|
import type { ComponentProps } from "react";
|
||||||
import { ToolCallPart } from "@x/shared/dist/message.js";
|
import { ToolCallPart } from "@x/shared/dist/message.js";
|
||||||
import { ToolPermissionMetadata } from "@x/shared/dist/runs.js";
|
import { ToolPermissionMetadata } from "@x/shared/dist/runs.js";
|
||||||
|
|
@ -21,6 +21,7 @@ export type PermissionRequestProps = ComponentProps<"div"> & {
|
||||||
onApproveSession?: () => void;
|
onApproveSession?: () => void;
|
||||||
onApproveAlways?: () => void;
|
onApproveAlways?: () => void;
|
||||||
onDeny?: () => void;
|
onDeny?: () => void;
|
||||||
|
onSwitchAgent?: (newAgent: 'claude' | 'codex') => void;
|
||||||
isProcessing?: boolean;
|
isProcessing?: boolean;
|
||||||
response?: 'approve' | 'deny' | null;
|
response?: 'approve' | 'deny' | null;
|
||||||
permission?: z.infer<typeof ToolPermissionMetadata>;
|
permission?: z.infer<typeof ToolPermissionMetadata>;
|
||||||
|
|
@ -41,6 +42,7 @@ export const PermissionRequest = ({
|
||||||
onApproveSession,
|
onApproveSession,
|
||||||
onApproveAlways,
|
onApproveAlways,
|
||||||
onDeny,
|
onDeny,
|
||||||
|
onSwitchAgent,
|
||||||
isProcessing = false,
|
isProcessing = false,
|
||||||
response = null,
|
response = null,
|
||||||
permission,
|
permission,
|
||||||
|
|
@ -54,6 +56,17 @@ export const PermissionRequest = ({
|
||||||
: null;
|
: null;
|
||||||
const filePermission = permission?.kind === "file" ? permission : 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 isResponded = response !== null;
|
||||||
const isApproved = response === 'approve';
|
const isApproved = response === 'approve';
|
||||||
|
|
||||||
|
|
@ -89,6 +102,15 @@ export const PermissionRequest = ({
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">
|
<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>
|
{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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{isResponded && (
|
{isResponded && (
|
||||||
|
|
@ -201,6 +223,18 @@ export const PermissionRequest = ({
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{otherAgent && otherDisplay && onSwitchAgent && (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onSwitchAgent(otherAgent)}
|
||||||
|
disabled={isProcessing}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
<RefreshCwIcon className="size-4" />
|
||||||
|
Use {otherDisplay} instead
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue