11import React from "react" ;
22import { useAPI } from "@/browser/contexts/API" ;
3- import { Button } from "@/browser/components/ui/button" ;
43import { Input } from "@/browser/components/ui/input" ;
4+ import { Button } from "@/browser/components/ui/button" ;
5+
6+ const MAX_PARALLEL_AGENT_TASKS_MIN = 1 ;
7+ const MAX_PARALLEL_AGENT_TASKS_MAX = 10 ;
8+ const MAX_TASK_NESTING_DEPTH_MIN = 1 ;
9+ const MAX_TASK_NESTING_DEPTH_MAX = 5 ;
10+
11+ function clampNumber ( value : number , min : number , max : number ) : number {
12+ return Math . min ( max , Math . max ( min , value ) ) ;
13+ }
14+
15+ function parseIntOrNull ( value : string ) : number | null {
16+ const parsed = Number . parseInt ( value , 10 ) ;
17+ return Number . isNaN ( parsed ) ? null : parsed ;
18+ }
519
620export function TasksSection ( ) {
721 const { api } = useAPI ( ) ;
822
923 const [ maxParallelAgentTasks , setMaxParallelAgentTasks ] = React . useState < number > ( 3 ) ;
24+ const [ maxParallelAgentTasksInput , setMaxParallelAgentTasksInput ] = React . useState < string > ( "3" ) ;
1025 const [ maxTaskNestingDepth , setMaxTaskNestingDepth ] = React . useState < number > ( 3 ) ;
26+ const [ maxTaskNestingDepthInput , setMaxTaskNestingDepthInput ] = React . useState < string > ( "3" ) ;
27+ const [ error , setError ] = React . useState < string | null > ( null ) ;
1128 const [ isSaving , setIsSaving ] = React . useState ( false ) ;
1229 const [ isLoading , setIsLoading ] = React . useState ( true ) ;
1330
@@ -29,7 +46,10 @@ export function TasksSection() {
2946 }
3047
3148 setMaxParallelAgentTasks ( settings . maxParallelAgentTasks ) ;
49+ setMaxParallelAgentTasksInput ( String ( settings . maxParallelAgentTasks ) ) ;
3250 setMaxTaskNestingDepth ( settings . maxTaskNestingDepth ) ;
51+ setMaxTaskNestingDepthInput ( String ( settings . maxTaskNestingDepth ) ) ;
52+ setError ( null ) ;
3353 } finally {
3454 if ( ! cancelled ) {
3555 setIsLoading ( false ) ;
@@ -47,16 +67,57 @@ export function TasksSection() {
4767 return ;
4868 }
4969
70+ const parsedMaxParallelAgentTasks = parseIntOrNull ( maxParallelAgentTasksInput ) ;
71+ const parsedMaxTaskNestingDepth = parseIntOrNull ( maxTaskNestingDepthInput ) ;
72+
73+ if ( parsedMaxParallelAgentTasks === null || parsedMaxTaskNestingDepth === null ) {
74+ setError ( "Please enter valid numbers for task limits." ) ;
75+ setMaxParallelAgentTasksInput ( String ( maxParallelAgentTasks ) ) ;
76+ setMaxTaskNestingDepthInput ( String ( maxTaskNestingDepth ) ) ;
77+ return ;
78+ }
79+
80+ const nextMaxParallelAgentTasks = clampNumber (
81+ parsedMaxParallelAgentTasks ,
82+ MAX_PARALLEL_AGENT_TASKS_MIN ,
83+ MAX_PARALLEL_AGENT_TASKS_MAX
84+ ) ;
85+ const nextMaxTaskNestingDepth = clampNumber (
86+ parsedMaxTaskNestingDepth ,
87+ MAX_TASK_NESTING_DEPTH_MIN ,
88+ MAX_TASK_NESTING_DEPTH_MAX
89+ ) ;
90+
91+ setMaxParallelAgentTasks ( nextMaxParallelAgentTasks ) ;
92+ setMaxParallelAgentTasksInput ( String ( nextMaxParallelAgentTasks ) ) ;
93+ setMaxTaskNestingDepth ( nextMaxTaskNestingDepth ) ;
94+ setMaxTaskNestingDepthInput ( String ( nextMaxTaskNestingDepth ) ) ;
95+
5096 setIsSaving ( true ) ;
97+ setError ( null ) ;
5198 try {
5299 await api . tasks . setTaskSettings ( {
53- maxParallelAgentTasks,
54- maxTaskNestingDepth,
100+ maxParallelAgentTasks : nextMaxParallelAgentTasks ,
101+ maxTaskNestingDepth : nextMaxTaskNestingDepth ,
55102 } ) ;
103+
104+ const saved = await api . tasks . getTaskSettings ( ) ;
105+ setMaxParallelAgentTasks ( saved . maxParallelAgentTasks ) ;
106+ setMaxParallelAgentTasksInput ( String ( saved . maxParallelAgentTasks ) ) ;
107+ setMaxTaskNestingDepth ( saved . maxTaskNestingDepth ) ;
108+ setMaxTaskNestingDepthInput ( String ( saved . maxTaskNestingDepth ) ) ;
109+ } catch ( err ) {
110+ setError ( err instanceof Error ? err . message : "Failed to save task settings" ) ;
56111 } finally {
57112 setIsSaving ( false ) ;
58113 }
59- } , [ api , maxParallelAgentTasks , maxTaskNestingDepth ] ) ;
114+ } , [
115+ api ,
116+ maxParallelAgentTasks ,
117+ maxParallelAgentTasksInput ,
118+ maxTaskNestingDepth ,
119+ maxTaskNestingDepthInput ,
120+ ] ) ;
60121
61122 return (
62123 < div className = "space-y-4" >
@@ -73,11 +134,30 @@ export function TasksSection() {
73134 < label className = "text-sm" > Max parallel subagents</ label >
74135 < Input
75136 type = "number"
76- min = { 1 }
77- max = { 10 }
78- value = { maxParallelAgentTasks }
137+ min = { MAX_PARALLEL_AGENT_TASKS_MIN }
138+ max = { MAX_PARALLEL_AGENT_TASKS_MAX }
139+ step = { 1 }
140+ value = { maxParallelAgentTasksInput }
79141 disabled = { isLoading }
80- onChange = { ( e ) => setMaxParallelAgentTasks ( Number ( e . target . value ) ) }
142+ onChange = { ( e ) => {
143+ setMaxParallelAgentTasksInput ( e . target . value ) ;
144+ setError ( null ) ;
145+ } }
146+ onBlur = { ( e ) => {
147+ const parsed = parseIntOrNull ( e . target . value ) ;
148+ if ( parsed === null ) {
149+ setMaxParallelAgentTasksInput ( String ( maxParallelAgentTasks ) ) ;
150+ return ;
151+ }
152+
153+ const clamped = clampNumber (
154+ parsed ,
155+ MAX_PARALLEL_AGENT_TASKS_MIN ,
156+ MAX_PARALLEL_AGENT_TASKS_MAX
157+ ) ;
158+ setMaxParallelAgentTasks ( clamped ) ;
159+ setMaxParallelAgentTasksInput ( String ( clamped ) ) ;
160+ } }
81161 />
82162 </ div >
83163 </ div >
@@ -87,18 +167,39 @@ export function TasksSection() {
87167 < label className = "text-sm" > Max nesting depth</ label >
88168 < Input
89169 type = "number"
90- min = { 1 }
91- max = { 5 }
92- value = { maxTaskNestingDepth }
170+ min = { MAX_TASK_NESTING_DEPTH_MIN }
171+ max = { MAX_TASK_NESTING_DEPTH_MAX }
172+ step = { 1 }
173+ value = { maxTaskNestingDepthInput }
93174 disabled = { isLoading }
94- onChange = { ( e ) => setMaxTaskNestingDepth ( Number ( e . target . value ) ) }
175+ onChange = { ( e ) => {
176+ setMaxTaskNestingDepthInput ( e . target . value ) ;
177+ setError ( null ) ;
178+ } }
179+ onBlur = { ( e ) => {
180+ const parsed = parseIntOrNull ( e . target . value ) ;
181+ if ( parsed === null ) {
182+ setMaxTaskNestingDepthInput ( String ( maxTaskNestingDepth ) ) ;
183+ return ;
184+ }
185+
186+ const clamped = clampNumber (
187+ parsed ,
188+ MAX_TASK_NESTING_DEPTH_MIN ,
189+ MAX_TASK_NESTING_DEPTH_MAX
190+ ) ;
191+ setMaxTaskNestingDepth ( clamped ) ;
192+ setMaxTaskNestingDepthInput ( String ( clamped ) ) ;
193+ } }
95194 />
96195 </ div >
97196 </ div >
98197
99198 < Button onClick = { ( ) => void onSave ( ) } disabled = { isLoading || isSaving } >
100199 { isSaving ? "Saving..." : "Save" }
101200 </ Button >
201+
202+ { error && < div className = "text-error text-sm" > { error } </ div > }
102203 </ div >
103204 </ div >
104205 ) ;
0 commit comments