@@ -144,6 +144,14 @@ export interface UseAutonomousRunConfigOptions {
144144 * (from {@link initialInput}) still wins.
145145 */
146146 defaultTimeoutHours ?: number ;
147+ /**
148+ * The host is the AI builder (a build / plan-authoring pass), not a live launch. Plan mode is
149+ * untimed server-side, so the time budget is meaningless here: we always show {@link
150+ * defaultTimeoutHours} (ignoring any timeout a previously-saved build config carried) and omit
151+ * `timeout_seconds` from the built payload so a stale value can never be persisted and surfaced
152+ * back as a misleading budget on the next open.
153+ */
154+ isPlanMode ?: boolean ;
147155}
148156
149157/** Fallback time budget (hours) when a host does not override it: autonomous execution is long-lived. */
@@ -194,6 +202,7 @@ export const useAutonomousRunConfig = ({
194202 initialInput,
195203 defaultObjective,
196204 defaultTimeoutHours = DEFAULT_TIMEOUT_HOURS ,
205+ isPlanMode = false ,
197206} : UseAutonomousRunConfigOptions ) : AutonomousRunConfig => {
198207 const [ templates , setTemplates ] = useState < AutonomousObjectiveTemplate [ ] > ( [ ] ) ;
199208 const [ loadingTemplates , setLoadingTemplates ] = useState ( false ) ;
@@ -284,9 +293,10 @@ export const useAutonomousRunConfig = ({
284293 setName ( initialInput . name ?? '' ) ;
285294 setDescription ( initialInput . description ?? '' ) ;
286295 // A saved timeout wins; otherwise fall back to this host's default (24h launch / 1h planning)
287- // rather than leaving whatever a previous open left behind.
296+ // rather than leaving whatever a previous open left behind. In plan mode the budget is
297+ // server-untimed and meaningless, so always show the default (never a stale saved value).
288298 setTimeoutHours (
289- initialInput . timeout_seconds && initialInput . timeout_seconds > 0
299+ ! isPlanMode && initialInput . timeout_seconds && initialInput . timeout_seconds > 0
290300 ? Math . round ( initialInput . timeout_seconds / 3600 )
291301 : defaultTimeoutHours ,
292302 ) ;
@@ -302,7 +312,7 @@ export const useAutonomousRunConfig = ({
302312 setObjective ( defaultObjective ) ;
303313 }
304314 }
305- } , [ open , initialInput , defaultObjective , defaultTimeoutHours ] ) ;
315+ } , [ open , initialInput , defaultObjective , defaultTimeoutHours , isPlanMode ] ) ;
306316
307317 const selectTemplate = ( template : AutonomousObjectiveTemplate ) => {
308318 setSelectedTemplateKey ( template . autonomous_objective_template_key ) ;
@@ -363,27 +373,39 @@ export const useAutonomousRunConfig = ({
363373 setAgentsLoaded ( false ) ;
364374 } ;
365375
366- const buildInput = ( planMode = false ) : AutonomousRunCreateInput => ( {
367- objective : objective . trim ( ) ,
368- objective_template_key : selectedTemplateKey ?? undefined ,
369- name : name . trim ( ) || undefined ,
370- description : description . trim ( ) || undefined ,
371- scope_rules : scopeRules . length > 0 ? scopeRules : undefined ,
372- // Authoritative selection: send it as soon as the agents loaded (even empty, i.e. the operator
373- // disabled every agent). Only omit it when the fetch failed, so the backend can fall back to
374- // the tenant defaults rather than silently running with no specialist agents.
375- agent_ids : agentsLoaded ? selectedAgentIds : undefined ,
376- agent_modes : agentsLoaded
377- ? {
378- [ ORCHESTRATOR_AGENT_ID ] : selectedAgentModes [ ORCHESTRATOR_AGENT_ID ] ?? ORCHESTRATOR_DEFAULT_DISCOVERY_MODE ,
379- ...Object . fromEntries ( selectedAgentIds . map ( id => [ id , selectedAgentModes [ id ] ?? SPECIALIST_DEFAULT_DISCOVERY_MODE ] ) ) ,
380- }
381- : undefined ,
382- plan_mode : planMode || undefined ,
383- // OpenAEV-enforced run deadline (seconds). Clamp to the advertised 1h-720h range (the HTML
384- // min/max only guard the spinner, not typed input); ignored server-side in build mode.
385- timeout_seconds : Math . min ( 720 * 3600 , Math . max ( 3600 , Math . round ( timeoutHours * 3600 ) ) ) ,
386- } ) ;
376+ const buildInput = ( planMode = false ) : AutonomousRunCreateInput => {
377+ // A plan-mode HOST builds a plan payload whatever action was pressed: the AI builder's primary
378+ // "Build" action goes through the generic launch callback (buildInput(false)), so keying off
379+ // the per-call argument alone would still carry timeout_seconds (which the build flow persists
380+ // as the scenario's saved config) and omit plan_mode. A live-launch host (isPlanMode false)
381+ // keeps the per-call argument as the sole switch.
382+ const effectivePlanMode = isPlanMode || planMode ;
383+ return {
384+ objective : objective . trim ( ) ,
385+ objective_template_key : selectedTemplateKey ?? undefined ,
386+ name : name . trim ( ) || undefined ,
387+ description : description . trim ( ) || undefined ,
388+ scope_rules : scopeRules . length > 0 ? scopeRules : undefined ,
389+ // Authoritative selection: send it as soon as the agents loaded (even empty, i.e. the operator
390+ // disabled every agent). Only omit it when the fetch failed, so the backend can fall back to
391+ // the tenant defaults rather than silently running with no specialist agents.
392+ agent_ids : agentsLoaded ? selectedAgentIds : undefined ,
393+ agent_modes : agentsLoaded
394+ ? {
395+ [ ORCHESTRATOR_AGENT_ID ] : selectedAgentModes [ ORCHESTRATOR_AGENT_ID ] ?? ORCHESTRATOR_DEFAULT_DISCOVERY_MODE ,
396+ ...Object . fromEntries ( selectedAgentIds . map ( id => [ id , selectedAgentModes [ id ] ?? SPECIALIST_DEFAULT_DISCOVERY_MODE ] ) ) ,
397+ }
398+ : undefined ,
399+ plan_mode : effectivePlanMode || undefined ,
400+ // OpenAEV-enforced run deadline (seconds). Clamp to the advertised 1h-720h range (the HTML
401+ // min/max only guard the spinner, not typed input). Omitted entirely in build/plan mode: the
402+ // server does not enforce a deadline while planning, and persisting one would carry a stale,
403+ // misleading budget back into the next open of the saved config.
404+ timeout_seconds : effectivePlanMode
405+ ? undefined
406+ : Math . min ( 720 * 3600 , Math . max ( 3600 , Math . round ( timeoutHours * 3600 ) ) ) ,
407+ } ;
408+ } ;
387409
388410 return {
389411 templates,
0 commit comments