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
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
ErrorContainer,
GenerateButton,
} from "@features/git-interaction/components/GitInteractionDialogs";
import { useFixWithAgent } from "@features/git-interaction/hooks/useFixWithAgent";
import { useGitInteractionStore } from "@features/git-interaction/state/gitInteractionStore";
import type { CreatePrStep } from "@features/git-interaction/types";
import type { DiffStats } from "@features/git-interaction/utils/diffStats";
import { buildCreatePrFlowErrorPrompt } from "@features/git-interaction/utils/errorPrompts";
import {
CheckCircle,
Circle,
Expand Down Expand Up @@ -133,6 +135,9 @@ export function CreatePrDialog({
}: CreatePrDialogProps) {
const store = useGitInteractionStore();
const { actions } = store;
const { canFixWithAgent, fixWithAgent } = useFixWithAgent(() =>
buildCreatePrFlowErrorPrompt(store.createPrFailedStep),
);

const { createPrStep: step } = store;
const isExecuting = step !== "idle" && step !== "complete";
Expand Down Expand Up @@ -299,7 +304,17 @@ export function CreatePrDialog({
/>

{step === "error" && store.createPrError && (
<ErrorContainer error={store.createPrError} />
<ErrorContainer
error={store.createPrError}
onFixWithAgent={
canFixWithAgent
? () => {
fixWithAgent(store.createPrError ?? "");
actions.closeCreatePr();
}
: undefined
}
/>
)}

<Flex gap="2" justify="end">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ import { useState } from "react";

const ICON_SIZE = 14;

export function ErrorContainer({ error }: { error: string }) {
export function ErrorContainer({
error,
onFixWithAgent,
}: {
error: string;
onFixWithAgent?: () => void;
}) {
const [copied, setCopied] = useState(false);

const handleCopy = async () => {
Expand Down Expand Up @@ -59,16 +65,30 @@ export function ErrorContainer({ error }: { error: string }) {
>
{error}
</Text>
<Tooltip content={copied ? "Copied!" : "Copy error"}>
<IconButton
size="1"
variant="ghost"
color="gray"
onClick={handleCopy}
>
<Copy size={12} weight={copied ? "fill" : "regular"} />
</IconButton>
</Tooltip>
<Flex gap="1" style={{ flexShrink: 0 }}>
{onFixWithAgent && (
<Tooltip content="Fix with Agent">
<IconButton
size="1"
variant="ghost"
color="gray"
onClick={onFixWithAgent}
>
<Sparkle size={12} />
</IconButton>
</Tooltip>
)}
<Tooltip content={copied ? "Copied!" : "Copy error"}>
<IconButton
size="1"
variant="ghost"
color="gray"
onClick={handleCopy}
>
<Copy size={12} weight={copied ? "fill" : "regular"} />
</IconButton>
</Tooltip>
</Flex>
</Flex>
</Flex>
</Box>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { DEFAULT_TAB_IDS } from "@features/panels/constants/panelConstants";
import { usePanelLayoutStore } from "@features/panels/store/panelLayoutStore";
import { findTabInTree } from "@features/panels/store/panelTree";
import { getSessionService } from "@features/sessions/service/service";
import { useSessionForTask } from "@features/sessions/stores/sessionStore";
import { useNavigationStore } from "@stores/navigationStore";
import { useCallback } from "react";
import type { FixWithAgentPrompt } from "../utils/errorPrompts";

/**
* Hook that sends a structured error prompt to the active agent session.
* Derives taskId and session readiness from stores.
*
* `canFixWithAgent` is true when there's an active, connected session.
*/
export function useFixWithAgent(
buildPrompt: (error: string) => FixWithAgentPrompt,
): {
canFixWithAgent: boolean;
fixWithAgent: (error: string) => Promise<void>;
} {
const taskId = useNavigationStore((s) =>
s.view.type === "task-detail" ? s.view.data?.id : undefined,
);
const session = useSessionForTask(taskId);
const isSessionReady = session?.status === "connected";

const canFixWithAgent = !!(taskId && isSessionReady);

const fixWithAgent = useCallback(
async (error: string) => {
if (!taskId || !isSessionReady) return;

const { label, context } = buildPrompt(error);

const prompt = `<error_context label="${label}">${context}</error_context>\n\n\`\`\`\n${error}\n\`\`\``;
getSessionService().sendPrompt(taskId, prompt);

const { taskLayouts, setActiveTab } = usePanelLayoutStore.getState();
const layout = taskLayouts[taskId];
if (layout) {
const result = findTabInTree(layout.panelTree, DEFAULT_TAB_IDS.LOGS);
if (result) {
setActiveTab(taskId, result.panelId, DEFAULT_TAB_IDS.LOGS);
}
}
},
[buildPrompt, taskId, isSessionReady],
);

return { canFixWithAgent, fixWithAgent };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { CreatePrStep } from "../types";

export interface FixWithAgentPrompt {
label: string;
context: string;
}

export function buildCreatePrFlowErrorPrompt(
failedStep: CreatePrStep | null,
): FixWithAgentPrompt {
return {
label: `Fix PR creation error`,
context: `The user tried to create a pull request using the Create PR button in the UI, but it failed at the ${failedStep} step.

This flow is supposed to follow these steps:
1. [creating-branch] Create a new feature branch, if needed (required if on default branch, optional otherwise)
2. [committing] Commit changes
3. [pushing] Push to remote
4. [creating-pr] Create PR

When an error occurs, the app automatically performs a rollback. This means you are likely in the pre-error state, e.g. back on the user's original branch without any new commits.

Rollback scenarios:
1. Branch creation fails -> check out user's original branch
2. Commit fails -> use git reset to get back to the user's original state

Common errors and resolutions:
- **Duplicate branch names** - guide the user towards using a different branch name, or sorting out any git issues that have led them to this state
- **Commit hook failures** - this may be the result of missing dependencies, check failure (e.g. lints), or something else. Ensure you fully understand the issue before proceeding.

Your task is to help the user diagnose and fix the underlying issue (e.g. resolve merge conflicts, fix authentication, clean up git state).

IMPORTANT:
- Do NOT attempt to complete the PR flow yourself (no commit, push, or gh pr create). The user will retry via the "Create PR" button once the issue is resolved.
- You may fix the underlying issue, but always confirm destructive actions with the user first.
- Start by diagnosing: run read-only commands to understand the current state before taking action.
- When the issue is resolved, remind the user to retry by clicking the "Create PR" button in the UI.`,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import {
baseComponents,
defaultRemarkPlugins,
} from "@features/editor/components/MarkdownRenderer";
import { File, GithubLogo } from "@phosphor-icons/react";
import { File, GithubLogo, Warning } from "@phosphor-icons/react";
import { Code, Text } from "@radix-ui/themes";
import type { ReactNode } from "react";
import { memo } from "react";
import type { Components } from "react-markdown";
import ReactMarkdown from "react-markdown";

const MENTION_TAG_REGEX =
/<file\s+path="([^"]+)"\s*\/>|<github_issue\s+number="([^"]+)"(?:\s+title="([^"]*)")?(?:\s+url="([^"]*)")?\s*\/>/g;
const MENTION_TAG_TEST = /<(?:file\s+path|github_issue\s+number)="[^"]+"/;
/<file\s+path="([^"]+)"\s*\/>|<github_issue\s+number="([^"]+)"(?:\s+title="([^"]*)")?(?:\s+url="([^"]*)")?\s*\/>|<error_context\s+label="([^"]*)">[\s\S]*?<\/error_context>/g;
const MENTION_TAG_TEST =
/<(?:file\s+path|github_issue\s+number|error_context\s+label)="[^"]+"/;

const inlineComponents: Components = {
...baseComponents,
Expand Down Expand Up @@ -113,6 +114,14 @@ export function parseMentionTags(content: string): ReactNode[] {
onClick={issueUrl ? () => window.open(issueUrl, "_blank") : undefined}
/>,
);
} else if (match[5]) {
parts.push(
<MentionChip
key={`error-ctx-${matchIndex}`}
icon={<Warning size={12} />}
label={match[5]}
/>,
);
}

lastIndex = matchIndex + match[0].length;
Expand Down
Loading