Add a required editable Agent prompt, prefilled from the Forgejo issue title, URL, body, and expected working notes.

This commit is contained in:
null 2026-05-24 19:49:46 -05:00
parent 922a386871
commit f71e0767a4
1 changed files with 84 additions and 40 deletions

View File

@ -1,6 +1,8 @@
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { ExternalLink } from "lucide-react";
import {
useListAgentsApiV1AgentsGet,
@ -30,6 +32,31 @@ type AssignIssueAgentDialogProps = {
onSuccess: (result: AssignIssueAgentResponse) => void;
};
function buildDefaultAgentPrompt(issue: ForgejoIssue, repositoryName: string) {
const issueBody = (issue.body ?? issue.body_preview ?? "").trim();
const lines = [
`Work on ${repositoryName}#${issue.forgejo_issue_number}: ${issue.title}`,
"",
"Use this Forgejo issue as the source of truth.",
`Issue URL: ${issue.html_url}`,
];
if (issueBody) {
lines.push("", "Issue details:", issueBody);
}
lines.push(
"",
"Expected approach:",
"- Read the relevant Pipeline code before editing.",
"- Keep the work scoped to this issue.",
"- Run targeted validation and report what passed.",
"- Post progress, blockers, and handoff notes back to the Pipeline task.",
);
return lines.join("\n");
}
export function AssignIssueAgentDialog({
issue,
repositoryName,
@ -46,6 +73,7 @@ export function AssignIssueAgentDialog({
const [error, setError] = useState<string | null>(null);
const [linkedBoards, setLinkedBoards] = useState<LinkedBoard[]>([]);
const [boardsLoading, setBoardsLoading] = useState(false);
const [linkedBoardsLoaded, setLinkedBoardsLoaded] = useState(false);
// Fetch only boards linked to this issue's repository — prevents picking a
// board that the backend will reject with "not linked to any board".
@ -53,6 +81,7 @@ export function AssignIssueAgentDialog({
if (!open || !issue) return;
let cancelled = false;
setBoardsLoading(true);
setLinkedBoardsLoaded(false);
setLinkedBoards([]);
setBoardId("");
getLinkedBoardsForRepository(issue.repository_id)
@ -65,7 +94,10 @@ export function AssignIssueAgentDialog({
if (!cancelled) setLinkedBoards([]);
})
.finally(() => {
if (!cancelled) setBoardsLoading(false);
if (!cancelled) {
setBoardsLoading(false);
setLinkedBoardsLoaded(true);
}
});
return () => { cancelled = true; };
}, [open, issue]);
@ -91,29 +123,37 @@ export function AssignIssueAgentDialog({
setInstructions("");
setStartImmediately(true);
setError(null);
setLinkedBoardsLoaded(false);
}
}, [open]);
useEffect(() => {
if (!open || !issue) return;
setInstructions(buildDefaultAgentPrompt(issue, repositoryName));
}, [open, issue, repositoryName]);
useEffect(() => {
setAgentId("");
}, [boardId]);
if (!issue) return null;
const noLinkedBoards = !boardsLoading && linkedBoards.length === 0;
const noLinkedBoards =
linkedBoardsLoaded && !boardsLoading && linkedBoards.length === 0;
const prompt = instructions.trim();
const handleSubmit = async () => {
if (!boardId) return;
if (!boardId || !agentId || !prompt) return;
setIsSubmitting(true);
setError(null);
try {
const result = await assignIssueToAgent(issue.id, {
board_id: boardId,
assigned_agent_id: agentId || undefined,
assigned_agent_id: agentId,
priority,
start_immediately: startImmediately,
status: startImmediately && agentId ? "in_progress" : "inbox",
instructions: instructions.trim() || undefined,
status: startImmediately ? "in_progress" : "inbox",
instructions: prompt,
});
onSuccess(result);
onOpenChange(false);
@ -183,12 +223,17 @@ export function AssignIssueAgentDialog({
<p className="mt-1 text-muted">
Before you can assign an agent, link{" "}
<span className="font-medium text-strong">{repositoryName}</span>{" "}
to a board in{" "}
<span className="font-medium text-strong">
Board Settings Git Repositories
</span>
.
to the board that should own this task. Open the target board and
use its Git Project repositories panel, or manage tracked
repositories first.
</p>
<Link
href="/git-projects/repositories"
className="mt-3 inline-flex h-9 items-center gap-2 rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-3 text-xs font-semibold text-strong transition hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
>
Manage Git repositories
<ExternalLink className="h-3.5 w-3.5" />
</Link>
</div>
) : (
<>
@ -221,10 +266,7 @@ export function AssignIssueAgentDialog({
<div className="space-y-1.5">
<label className="text-sm font-medium text-strong">
Agent{" "}
<span className="text-xs font-normal text-muted">
(optional leaves task unassigned if blank)
</span>
Agent <span className="text-[color:var(--danger)]">*</span>
</label>
<select
value={agentId}
@ -232,7 +274,7 @@ export function AssignIssueAgentDialog({
disabled={isSubmitting || !boardId || agentsQuery.isLoading}
className="w-full rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] px-3 py-2 text-sm text-strong focus:outline-none focus:ring-2 focus:ring-[color:var(--accent)]"
>
<option value="">Unassigned</option>
<option value="">Select an agent</option>
{agentsQuery.isLoading ? (
<option disabled>Loading agents</option>
) : (
@ -244,6 +286,11 @@ export function AssignIssueAgentDialog({
))
)}
</select>
{boardId && !agentsQuery.isLoading && agents.length === 0 ? (
<p className="text-xs text-muted">
No agents are available on this board yet.
</p>
) : null}
</div>
<div className="space-y-1.5">
@ -263,33 +310,34 @@ export function AssignIssueAgentDialog({
<div className="space-y-1.5">
<label className="text-sm font-medium text-strong">
Instructions{" "}
<span className="text-xs font-normal text-muted">(optional)</span>
Agent prompt <span className="text-[color:var(--danger)]">*</span>
</label>
<Textarea
value={instructions}
onChange={(e) => setInstructions(e.target.value)}
disabled={isSubmitting}
rows={3}
placeholder="Additional context or instructions for the agent…"
className="resize-none text-sm"
rows={9}
placeholder="Prompt the agent with the goal, context, and expected validation…"
className="resize-y text-sm"
/>
<p className="text-xs text-muted">
This prompt is copied into the Pipeline task and sent with the
assignment notification.
</p>
</div>
{agentId ? (
<div className="flex items-center gap-3">
{toggleSwitch(startImmediately, setStartImmediately, isSubmitting)}
<span className="space-y-0.5">
<span className="block text-sm font-medium text-strong">
Start immediately
</span>
<span className="block text-xs text-muted">
Set task to{" "}
<span className="font-mono">in_progress</span> on assign.
</span>
<div className="flex items-center gap-3">
{toggleSwitch(startImmediately, setStartImmediately, isSubmitting)}
<span className="space-y-0.5">
<span className="block text-sm font-medium text-strong">
Start immediately
</span>
</div>
) : null}
<span className="block text-xs text-muted">
Set task to <span className="font-mono">in_progress</span>{" "}
when assigned.
</span>
</span>
</div>
</>
)}
</div>
@ -311,13 +359,9 @@ export function AssignIssueAgentDialog({
{!noLinkedBoards && (
<Button
onClick={handleSubmit}
disabled={isSubmitting || !boardId}
disabled={isSubmitting || !boardId || !agentId || !prompt}
>
{isSubmitting
? "Assigning…"
: agentId
? "Assign Agent"
: "Create Task"}
{isSubmitting ? "Assigning…" : "Assign Agent"}
</Button>
)}
</DialogFooter>