diff --git a/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx b/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx
index b9d2ce8c..633423c3 100644
--- a/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx
+++ b/dashboards/tracker-web/src/app/dashboard/fleet/jobs/page.tsx
@@ -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 (
@@ -191,6 +213,34 @@ export default function FleetJobsPage() {
)}
+ {repo && (
+
+
+
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"
+ />
+
+ Runs in the checkout; the PR opens only if it passes.
+
+
+ )}
+ {repo && (
+
+ )}
diff --git a/dashboards/tracker-web/src/lib/fleet-client.ts b/dashboards/tracker-web/src/lib/fleet-client.ts
index ea02164b..666641de 100644
--- a/dashboards/tracker-web/src/lib/fleet-client.ts
+++ b/dashboards/tracker-web/src/lib/fleet-client.ts
@@ -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. */
diff --git a/services/platform-service/src/modules/fleet/coordinator.ts b/services/platform-service/src/modules/fleet/coordinator.ts
index 04e481f0..46765df5 100644
--- a/services/platform-service/src/modules/fleet/coordinator.ts
+++ b/services/platform-service/src/modules/fleet/coordinator.ts
@@ -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,
diff --git a/services/platform-service/src/modules/fleet/types.ts b/services/platform-service/src/modules/fleet/types.ts
index ef87bc3d..f84add31 100644
--- a/services/platform-service/src/modules/fleet/types.ts
+++ b/services/platform-service/src/modules/fleet/types.ts
@@ -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(