Story 33: Copy-paste diff commands for agent worktrees
- Add base_branch detection to WorktreeInfo (from project root HEAD)
- Expose base_branch in AgentInfo API response
- Add {{base_branch}} template variable to agent config rendering
- Show git difftool command with copy-to-clipboard in AgentPanel UI
- Add diff command instruction to coder agent prompts
- Add AgentPanel tests for diff command rendering and clipboard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ interface AgentState {
|
||||
log: string[];
|
||||
sessionId: string | null;
|
||||
worktreePath: string | null;
|
||||
baseBranch: string | null;
|
||||
}
|
||||
|
||||
const STATUS_COLORS: Record<AgentStatusValue, string> = {
|
||||
@@ -104,6 +105,69 @@ function agentKey(storyId: string, agentName: string): string {
|
||||
return `${storyId}:${agentName}`;
|
||||
}
|
||||
|
||||
function DiffCommand({
|
||||
worktreePath,
|
||||
baseBranch,
|
||||
}: { worktreePath: string; baseBranch: string }) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const command = `cd "${worktreePath}" && git difftool ${baseBranch}...HEAD`;
|
||||
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(command);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch {
|
||||
// Fallback: select text for manual copy
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
marginBottom: "6px",
|
||||
}}
|
||||
>
|
||||
<code
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: "0.7em",
|
||||
color: "#8b949e",
|
||||
background: "#0d1117",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #21262d",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{command}
|
||||
</code>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
style={{
|
||||
padding: "3px 8px",
|
||||
borderRadius: "4px",
|
||||
border: "1px solid #30363d",
|
||||
background: copied ? "#238636" : "#21262d",
|
||||
color: copied ? "#fff" : "#8b949e",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.7em",
|
||||
fontWeight: 600,
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{copied ? "Copied" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentPanel({ stories }: AgentPanelProps) {
|
||||
const [agents, setAgents] = useState<Record<string, AgentState>>({});
|
||||
const [roster, setRoster] = useState<AgentConfigInfo[]>([]);
|
||||
@@ -133,6 +197,7 @@ export function AgentPanel({ stories }: AgentPanelProps) {
|
||||
log: [],
|
||||
sessionId: a.session_id,
|
||||
worktreePath: a.worktree_path,
|
||||
baseBranch: a.base_branch,
|
||||
};
|
||||
if (a.status === "running" || a.status === "pending") {
|
||||
subscribeToAgent(a.story_id, a.agent_name);
|
||||
@@ -166,6 +231,7 @@ export function AgentPanel({ stories }: AgentPanelProps) {
|
||||
log: [],
|
||||
sessionId: null,
|
||||
worktreePath: null,
|
||||
baseBranch: null,
|
||||
};
|
||||
|
||||
switch (event.type) {
|
||||
@@ -241,6 +307,7 @@ export function AgentPanel({ stories }: AgentPanelProps) {
|
||||
log: [],
|
||||
sessionId: info.session_id,
|
||||
worktreePath: info.worktree_path,
|
||||
baseBranch: info.base_branch,
|
||||
},
|
||||
}));
|
||||
setExpandedKey(key);
|
||||
@@ -609,6 +676,12 @@ export function AgentPanel({ stories }: AgentPanelProps) {
|
||||
Worktree: {a.worktreePath}
|
||||
</div>
|
||||
)}
|
||||
{a.worktreePath && (
|
||||
<DiffCommand
|
||||
worktreePath={a.worktreePath}
|
||||
baseBranch={a.baseBranch ?? "master"}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
maxHeight: "300px",
|
||||
|
||||
Reference in New Issue
Block a user