feat(fleet): per-repo verify + auto-merge options for PR jobs

Add job-level verify (command run in the PR checkout before opening the PR) and
autoMerge (squash-merge the PR once opened). Surfaced in the New Job form as a
Verify-command field + Auto-merge checkbox (PR mode only); confirmation now shows
PR-mode/repo. More repos added to the dropdown.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
saravanakumardb1 2026-05-31 06:17:19 -07:00
parent 2adddce754
commit c239abeec9
4 changed files with 64 additions and 3 deletions

View File

@ -40,6 +40,11 @@ const FLEET_REPOS = [
'learning_ai_2nd_brain',
'learning_ai_auth_app',
'learning_agent_monitoring_fx',
'learning_notif_scanr',
'learning_ai_local_llms',
'learning_ai_mac_tooling',
'learning_ai_productivity_web',
'learning_ai_webui_copilot',
];
const FLEET_BASE_BRANCH = 'main';
@ -55,6 +60,8 @@ export default function FleetJobsPage() {
const [priority, setPriority] = useState<'critical' | 'high' | 'medium' | 'low'>('high');
const [caps, setCaps] = useState('build');
const [repo, setRepo] = useState('');
const [verifyCmd, setVerifyCmd] = useState('');
const [autoMerge, setAutoMerge] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [submitMsg, setSubmitMsg] = useState<{ ok: boolean; text: string } | null>(null);
@ -95,9 +102,24 @@ export default function FleetJobsPage() {
bodyMd: body.trim(),
priority,
capabilities,
...(repo ? { repo, baseBranch: FLEET_BASE_BRANCH } : {}),
...(repo
? {
repo,
baseBranch: FLEET_BASE_BRANCH,
...(verifyCmd.trim() ? { verify: verifyCmd.trim() } : {}),
...(autoMerge ? { autoMerge: true } : {}),
}
: {}),
});
setSubmitMsg({
ok: true,
text:
`Submitted ${job.id} (stage: ${job.stage})` +
(repo
? ` — PR mode: ${repo}@${FLEET_BASE_BRANCH}${verifyCmd.trim() ? ' +verify' : ''}${autoMerge ? ' +auto-merge' : ''}`
: ' — no PR (plain job)') +
'.',
});
setSubmitMsg({ ok: true, text: `Submitted ${job.id} (stage: ${job.stage}).` });
setBody('');
await refresh();
} catch (err: unknown) {
@ -106,7 +128,7 @@ export default function FleetJobsPage() {
} finally {
setSubmitting(false);
}
}, [body, caps, priority, repo, refresh]);
}, [body, caps, priority, repo, verifyCmd, autoMerge, refresh]);
return (
<div className="p-6 space-y-6">
@ -191,6 +213,34 @@ export default function FleetJobsPage() {
</p>
)}
</div>
{repo && (
<div>
<label htmlFor="job-verify" className="mb-1 block text-sm font-medium">
Verify command (optional)
</label>
<input
id="job-verify"
value={verifyCmd}
onChange={e => setVerifyCmd(e.target.value)}
placeholder="e.g. pnpm install && pnpm test"
className="w-64 rounded border bg-background px-2 py-1 text-sm font-mono"
/>
<p className="mt-1 text-xs text-muted-foreground">
Runs in the checkout; the PR opens only if it passes.
</p>
</div>
)}
{repo && (
<label className="flex items-center gap-2 self-end pb-1 text-sm">
<input
type="checkbox"
checked={autoMerge}
onChange={e => setAutoMerge(e.target.checked)}
aria-label="Auto-merge the PR when opened"
/>
Auto-merge PR
</label>
)}
<Button onClick={handleSubmit} disabled={submitting} aria-label="Submit new job">
{submitting ? 'Submitting…' : 'Submit Job'}
</Button>

View File

@ -202,6 +202,9 @@ export interface SubmitJobBody {
/** PR mode: open a PR against this repo (`owner/name` or clone URL) + base branch. */
repo?: string;
baseBranch?: string;
/** PR mode: verify command run in the checkout before the PR opens; auto-merge the PR. */
verify?: string;
autoMerge?: boolean;
}
/** Submit a new fleet job. Returns the created job. */

View File

@ -214,6 +214,8 @@ export async function submitJob(productId: string, input: SubmitJobInput): Promi
trackerItemId: input.trackerItemId,
repo: input.repo,
baseBranch: input.baseBranch,
verify: input.verify,
autoMerge: input.autoMerge,
attempts: 0,
leaseEpoch: 0,
rev: 0,

View File

@ -185,6 +185,8 @@ export const FleetJobDocSchema = z.object({
*/
repo: z.string().optional(),
baseBranch: z.string().optional(),
verify: z.string().optional(),
autoMerge: z.boolean().optional(),
checkpoint: CheckpointSchema.optional(),
attempts: z.number().int().nonnegative().default(0),
leaseEpoch: z.number().int().nonnegative().default(0),
@ -352,6 +354,10 @@ export const SubmitJobSchema = z.object({
/** Optional PR target (§PR mode): repo (`owner/name` or clone URL) + base branch. */
repo: z.string().optional(),
baseBranch: z.string().optional(),
/** PR mode options: verify command to run in the checkout before opening the PR,
* and whether to auto-merge the PR once opened. */
verify: z.string().optional(),
autoMerge: z.boolean().optional(),
/** Phase 3 DAG: inline children to create atomically with the parent. */
children: z
.array(