diff --git a/apps/cli/src/commands/_cloud-agent-create.ts b/apps/cli/src/commands/_cloud-agent-create.ts index 58cff7d..e3d2c3b 100644 --- a/apps/cli/src/commands/_cloud-agent-create.ts +++ b/apps/cli/src/commands/_cloud-agent-create.ts @@ -23,7 +23,7 @@ import { makeProgressReporter } from '../lib/progress.js'; import { printLaunchRecap } from '../lib/launch-recap.js'; import { buildPromptArgs } from '../lib/queue/build-prompt-args.js'; import { buildResyncWarning } from '../lib/resync-warning.js'; -import { cloudAgentAttach } from './_cloud-attach.js'; +import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js'; export interface CloudAgentCreateArgs { /** Pre-resolved provider (from `providerForCreate`). */ @@ -42,11 +42,11 @@ export interface CloudAgentCreateArgs { verbose?: boolean; /** Where to open the attached session; forwarded to `cloudAgentAttach`. */ openIn?: AttachOpenIn; - /** When `false`, create the cloud box and skip the agent attach (background - * mode). Defaults to `true`. On cloud providers the agent's tmux session is - * created lazily by `cloudAgentAttach`; with `attach: false` the session - * isn't started yet — a later `agentbox attach ` starts it on - * first attach. */ + /** When `false`, create the cloud box and start the agent in a detached tmux + * session (background mode) but don't attach the host terminal — mirrors the + * docker path, where the session is always created and only the terminal + * attach is skipped. A later `agentbox attach ` finds the + * running session and attaches to it. Defaults to `true`. */ attach?: boolean; /** * Hook fired AFTER the box is provisioned and BEFORE the agent attach starts @@ -118,6 +118,15 @@ export async function cloudAgentCreate(args: CloudAgentCreateArgs): Promise start --no-attach` (and idle-resumed + // creates): the box already has a claude/codex session to reopen. The `-i` + // queue path always seeds a prompt, so extraArgs is non-empty and this no-ops. + let extraArgs = args.extraArgs; + if ( + (!extraArgs || extraArgs.length === 0) && + (args.binary === 'claude' || args.binary === 'codex') + ) { + const resume = await agentResumeArgs(provider, box, args.binary); + if (resume) extraArgs = resume; + } + const command = buildCloudAttachInnerCommand(args.binary, extraArgs); const { exitCode, stderr } = await startDetachedSession( provider, box, diff --git a/apps/cli/src/commands/claude.ts b/apps/cli/src/commands/claude.ts index 766566c..fea4b13 100644 --- a/apps/cli/src/commands/claude.ts +++ b/apps/cli/src/commands/claude.ts @@ -54,7 +54,7 @@ import { NO_ATTACH_HELP, resolveAttachInOption, } from './_attach-in.js'; -import { cloudAgentAttach } from './_cloud-attach.js'; +import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js'; import { cloudAgentCreate } from './_cloud-agent-create.js'; import { runCarryGate, runQueuedCarryGate } from '../lib/carry-gate.js'; import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js'; @@ -1410,12 +1410,6 @@ const claudeStartCommand = new Command('start') } } if ((box.provider ?? 'docker') !== 'docker') { - if (opts.attach === false) { - outro( - `--no-attach: cloud agent sessions are started lazily on attach. Run: agentbox claude attach ${reattachRef(box)}`, - ); - return; - } const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides: { ...(attachIn ? { attach: { openIn: attachIn } } : {}), @@ -1438,10 +1432,23 @@ const claudeStartCommand = new Command('start') throw err; } } + const sessionName = opts.sessionName ?? 'claude'; + if (opts.attach === false) { + // Background mode: start the detached session (matches docker) instead + // of deferring the agent until the next attach. + await cloudAgentStartDetached({ + box, + binary: 'claude', + sessionName, + extraArgs: effectiveClaudeArgs, + }); + outro(`--no-attach: claude started in background. Attach: agentbox claude attach ${reattachRef(box)}`); + return; + } await cloudAgentAttach({ box, binary: 'claude', - sessionName: opts.sessionName ?? 'claude', + sessionName, mode: 'claude', extraArgs: effectiveClaudeArgs, openIn: hostAwareOpenIn(cfg), diff --git a/apps/cli/src/commands/codex.ts b/apps/cli/src/commands/codex.ts index c766fea..4b5bcea 100644 --- a/apps/cli/src/commands/codex.ts +++ b/apps/cli/src/commands/codex.ts @@ -56,7 +56,7 @@ import { NO_ATTACH_HELP, resolveAttachInOption, } from './_attach-in.js'; -import { cloudAgentAttach } from './_cloud-attach.js'; +import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js'; import { cloudAgentCreate } from './_cloud-agent-create.js'; import { runCarryGate, runQueuedCarryGate } from '../lib/carry-gate.js'; import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js'; @@ -1044,12 +1044,6 @@ const codexStartCommand = new Command('start') } } if ((box.provider ?? 'docker') !== 'docker') { - if (opts.attach === false) { - outro( - `--no-attach: cloud agent sessions are started lazily on attach. Run: agentbox codex attach ${reattachRef(box)}`, - ); - return; - } const cfg = await loadEffectiveConfig(box.workspacePath, { cliOverrides: { ...(attachIn ? { attach: { openIn: attachIn } } : {}), @@ -1072,10 +1066,23 @@ const codexStartCommand = new Command('start') throw err; } } + const sessionName = opts.sessionName ?? 'codex'; + if (opts.attach === false) { + // Background mode: start the detached session (matches docker) instead + // of deferring the agent until the next attach. + await cloudAgentStartDetached({ + box, + binary: 'codex', + sessionName, + extraArgs: effectiveCodexArgs, + }); + outro(`--no-attach: codex started in background. Attach: agentbox codex attach ${reattachRef(box)}`); + return; + } await cloudAgentAttach({ box, binary: 'codex', - sessionName: opts.sessionName ?? 'codex', + sessionName, mode: 'codex', extraArgs: effectiveCodexArgs, openIn: hostAwareOpenIn(cfg), diff --git a/apps/cli/src/commands/opencode.ts b/apps/cli/src/commands/opencode.ts index b4829df..e1dc56a 100644 --- a/apps/cli/src/commands/opencode.ts +++ b/apps/cli/src/commands/opencode.ts @@ -53,7 +53,7 @@ import { NO_ATTACH_HELP, resolveAttachInOption, } from './_attach-in.js'; -import { cloudAgentAttach } from './_cloud-attach.js'; +import { cloudAgentAttach, cloudAgentStartDetached } from './_cloud-attach.js'; import { cloudAgentCreate } from './_cloud-agent-create.js'; import { runCarryGate, runQueuedCarryGate } from '../lib/carry-gate.js'; import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js'; @@ -886,19 +886,28 @@ const opencodeStartCommand = new Command('start') } } if ((box.provider ?? 'docker') !== 'docker') { + const cfg = await loadEffectiveConfig(box.workspacePath, { + cliOverrides: attachIn ? { attach: { openIn: attachIn } } : {}, + }); + const sessionName = opts.sessionName ?? 'opencode'; if (opts.attach === false) { + // Background mode: start the detached session (matches docker) instead + // of deferring the agent until the next attach. + await cloudAgentStartDetached({ + box, + binary: 'opencode', + sessionName, + extraArgs: effectiveOpencodeArgs, + }); outro( - `--no-attach: cloud agent sessions are started lazily on attach. Run: agentbox opencode attach ${reattachRef(box)}`, + `--no-attach: opencode started in background. Attach: agentbox opencode attach ${reattachRef(box)}`, ); return; } - const cfg = await loadEffectiveConfig(box.workspacePath, { - cliOverrides: attachIn ? { attach: { openIn: attachIn } } : {}, - }); await cloudAgentAttach({ box, binary: 'opencode', - sessionName: opts.sessionName ?? 'opencode', + sessionName, mode: 'opencode', extraArgs: effectiveOpencodeArgs, openIn: hostAwareOpenIn(cfg),