From 6b21d4b8bd8557d14e616327119863211ddf5e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 16 Aug 2026 02:11:49 +0000 Subject: [PATCH] duration is one checkbox, because two of the three options were the same thing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "This session" meant nothing. The backend deliberately holds the agent process alive while a background job is running, so closing the browser does not end one — "this session" and "until stopped" were one behaviour wearing two labels, and offering both implied a difference that does not exist. So there is one real choice, and it is a yes/no: bound it by time, or run until stopped. [ ] Stop after [60] minutes Unchecked is the default and the common case. Checked exits when the time is up whether or not it fired, which is the only thing a clock can usefully mean here. This also removes a note that had become false. The dialog used to say "all of these end if the session does" — written from the process tree, where the job is a descendant of the agent process. True as far as it went, and wrong in practice because of the keep-alive. It now says what each choice actually does instead of what the ancestry suggests. Still true, and unstated because it belongs somewhere else: `pm2 restart officer-claude-code` ends every running job. That is a deploy-time fact, not something to put in a composer. tsgo clean, frontend builds, 851 pass / 7 fail unchanged. Co-Authored-By: Claude Opus 5 --- .../src/apps/Chat/background-job-prompt.ts | 19 ++++++---- .../Chat/components/BackgroundJobDialog.tsx | 35 ++++++------------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/Chat/background-job-prompt.ts b/src/workspaces/officerdev/src/apps/Chat/background-job-prompt.ts index 59182667..e511f058 100644 --- a/src/workspaces/officerdev/src/apps/Chat/background-job-prompt.ts +++ b/src/workspaces/officerdev/src/apps/Chat/background-job-prompt.ts @@ -18,17 +18,22 @@ export type BackgroundJobSpec = { condition: string; /** Whether firing ends the job. Ignored without a condition. */ stopOnFire: boolean; - /** `session` = until this chat ends. A number = minutes. `until-stopped` = until told otherwise. */ - duration: 'session' | 'until-stopped' | number; + /** + * Minutes to run for, or `null` to run until stopped. + * + * There used to be a "this session" option. It meant nothing: the backend deliberately holds the agent + * process alive while a job is running, so closing the browser does not end one — "this session" and + * "until stopped" were the same thing wearing two labels. + */ + duration: number | null; /** Where it runs. The panel's own directory, so nothing has to be typed. */ cwd: string; }; -const durationLine = (d: BackgroundJobSpec['duration']): string => { - if (d === 'session') return 'Run until this session ends. Do not bound the loop by time.'; - if (d === 'until-stopped') return 'Run until I stop it. Do not bound the loop by time.'; - return `Run for about ${d} minute${d === 1 ? '' : 's'}, then exit 1 to say it timed out.`; -}; +const durationLine = (d: BackgroundJobSpec['duration']): string => + d === null + ? 'Run until I stop it. Do not bound the loop by time.' + : `Run for about ${d} minute${d === 1 ? '' : 's'}, then exit 1 to say it timed out.`; export function buildBackgroundJobPrompt(spec: BackgroundJobSpec): string { const watching = spec.condition.trim().length > 0; diff --git a/src/workspaces/officerdev/src/apps/Chat/components/BackgroundJobDialog.tsx b/src/workspaces/officerdev/src/apps/Chat/components/BackgroundJobDialog.tsx index ff8a948b..cb4a79b9 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/BackgroundJobDialog.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/BackgroundJobDialog.tsx @@ -34,15 +34,15 @@ type BackgroundJobDialogProps = { onSubmit: (intent: string, preamble: string) => void; }; -type Duration = 'session' | 'until-stopped' | 'timed'; - export const BackgroundJobDialog = ({ open, onOpenChange, cwd, onSubmit }: BackgroundJobDialogProps) => { const [mode, setMode] = useState<'command' | 'prompt'>('command'); const [body, setBody] = useState(''); const [watching, setWatching] = useState(false); const [condition, setCondition] = useState(''); const [stopOnFire, setStopOnFire] = useState(true); - const [duration, setDuration] = useState('session'); + // Unchecked means no time bound — the job runs until stopped. There is no third option: the backend + // keeps the agent alive while a job runs, so "this session" would have meant the same thing. + const [timed, setTimed] = useState(false); const [minutes, setMinutes] = useState(60); useEffect(() => { @@ -52,7 +52,7 @@ export const BackgroundJobDialog = ({ open, onOpenChange, cwd, onSubmit }: Backg setWatching(false); setCondition(''); setStopOnFire(true); - setDuration('session'); + setTimed(false); setMinutes(60); }, [open]); @@ -74,7 +74,7 @@ export const BackgroundJobDialog = ({ open, onOpenChange, cwd, onSubmit }: Backg body, condition: watching ? condition : '', stopOnFire, - duration: duration === 'timed' ? minutes : duration, + duration: timed ? minutes : null, cwd, }; // The intent is what the transcript shows; the preamble rides underneath it. @@ -151,36 +151,21 @@ export const BackgroundJobDialog = ({ open, onOpenChange, cwd, onSubmit }: Backg
- - - - {/* Said plainly rather than promised. The job is a descendant of this session's process, so - nothing here outlives the session — including "until stopped". */}

- All of these end if the session does — the job runs underneath it. + {timed ? 'Exits when the time is up, whether or not it fired.' : 'Runs until you stop it.'}