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:
parent
2adddce754
commit
c239abeec9
@ -40,6 +40,11 @@ const FLEET_REPOS = [
|
|||||||
'learning_ai_2nd_brain',
|
'learning_ai_2nd_brain',
|
||||||
'learning_ai_auth_app',
|
'learning_ai_auth_app',
|
||||||
'learning_agent_monitoring_fx',
|
'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';
|
const FLEET_BASE_BRANCH = 'main';
|
||||||
|
|
||||||
@ -55,6 +60,8 @@ export default function FleetJobsPage() {
|
|||||||
const [priority, setPriority] = useState<'critical' | 'high' | 'medium' | 'low'>('high');
|
const [priority, setPriority] = useState<'critical' | 'high' | 'medium' | 'low'>('high');
|
||||||
const [caps, setCaps] = useState('build');
|
const [caps, setCaps] = useState('build');
|
||||||
const [repo, setRepo] = useState('');
|
const [repo, setRepo] = useState('');
|
||||||
|
const [verifyCmd, setVerifyCmd] = useState('');
|
||||||
|
const [autoMerge, setAutoMerge] = useState(false);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [submitMsg, setSubmitMsg] = useState<{ ok: boolean; text: string } | null>(null);
|
const [submitMsg, setSubmitMsg] = useState<{ ok: boolean; text: string } | null>(null);
|
||||||
|
|
||||||
@ -95,9 +102,24 @@ export default function FleetJobsPage() {
|
|||||||
bodyMd: body.trim(),
|
bodyMd: body.trim(),
|
||||||
priority,
|
priority,
|
||||||
capabilities,
|
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('');
|
setBody('');
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@ -106,7 +128,7 @@ export default function FleetJobsPage() {
|
|||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
}, [body, caps, priority, repo, refresh]);
|
}, [body, caps, priority, repo, verifyCmd, autoMerge, refresh]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
@ -191,6 +213,34 @@ export default function FleetJobsPage() {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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">
|
<Button onClick={handleSubmit} disabled={submitting} aria-label="Submit new job">
|
||||||
{submitting ? 'Submitting…' : 'Submit Job'}
|
{submitting ? 'Submitting…' : 'Submit Job'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -202,6 +202,9 @@ export interface SubmitJobBody {
|
|||||||
/** PR mode: open a PR against this repo (`owner/name` or clone URL) + base branch. */
|
/** PR mode: open a PR against this repo (`owner/name` or clone URL) + base branch. */
|
||||||
repo?: string;
|
repo?: string;
|
||||||
baseBranch?: 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. */
|
/** Submit a new fleet job. Returns the created job. */
|
||||||
|
|||||||
@ -214,6 +214,8 @@ export async function submitJob(productId: string, input: SubmitJobInput): Promi
|
|||||||
trackerItemId: input.trackerItemId,
|
trackerItemId: input.trackerItemId,
|
||||||
repo: input.repo,
|
repo: input.repo,
|
||||||
baseBranch: input.baseBranch,
|
baseBranch: input.baseBranch,
|
||||||
|
verify: input.verify,
|
||||||
|
autoMerge: input.autoMerge,
|
||||||
attempts: 0,
|
attempts: 0,
|
||||||
leaseEpoch: 0,
|
leaseEpoch: 0,
|
||||||
rev: 0,
|
rev: 0,
|
||||||
|
|||||||
@ -185,6 +185,8 @@ export const FleetJobDocSchema = z.object({
|
|||||||
*/
|
*/
|
||||||
repo: z.string().optional(),
|
repo: z.string().optional(),
|
||||||
baseBranch: z.string().optional(),
|
baseBranch: z.string().optional(),
|
||||||
|
verify: z.string().optional(),
|
||||||
|
autoMerge: z.boolean().optional(),
|
||||||
checkpoint: CheckpointSchema.optional(),
|
checkpoint: CheckpointSchema.optional(),
|
||||||
attempts: z.number().int().nonnegative().default(0),
|
attempts: z.number().int().nonnegative().default(0),
|
||||||
leaseEpoch: 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. */
|
/** Optional PR target (§PR mode): repo (`owner/name` or clone URL) + base branch. */
|
||||||
repo: z.string().optional(),
|
repo: z.string().optional(),
|
||||||
baseBranch: 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. */
|
/** Phase 3 DAG: inline children to create atomically with the parent. */
|
||||||
children: z
|
children: z
|
||||||
.array(
|
.array(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user