feat: redesign CLI session modes and add session management commands#105
feat: redesign CLI session modes and add session management commands#105
Conversation
… --agent-mode flags - Default behavior with prompt: create async session, print ID to stdout, exit - --follow/-f: stream agent output, exit with agent's exit code - --interactive/-i: launch full TUI, auto-submit prompt (implies --agent-mode=interactive) - --agent-mode/-M: control how agent runs (async/interactive/plan) - Progress output goes to stderr for scriptability (SESSION=$(ox "task")) - Validation: -i/-f mutually exclusive, -f requires async agent mode - Add autoSubmitAgentMode to TUI runner for -i auto-submit support - Remove dead printSummary function - Update completion test for renamed flag
The process was staying alive after branchAction returned because background handles (port forwarding, analytics, etc.) kept the event loop running. Call process.exit(0) after printing the session ID.
Extract validateBranchOptions() and call it early in both the root command and branch subcommand actions, before the interactive/follow routing logic. Previously -f -i silently ignored -f because the interactive check came first and never reached branchAction's validation.
The PR creation instructions were gated on the 'detach' flag, which meant interactive/plan agents started in detached terminal mode would incorrectly get them. Gate on agentMode === 'async' instead, matching the comment's intent. Also remove the now-unused 'detach' field from StartContainerOptions since startContainer always runs containers detached (the caller uses provider.attach() for interactive sessions).
…n subcommands New 'ox session' command (alias: 'container') for managing individual sessions from the CLI. Subcommands: - rm/remove/delete <id>: Remove a session - stop <id>: Stop a running session - attach <id>: Raw terminal attach to a running session - ps/list/ls: List all sessions (shares impl with 'ox sessions') - logs [-f] [--tail n] <id>: Print/follow session logs - info [-o format] <id>: Show session details (table/json/yaml) - clean [-a] [-f]: Remove stopped containers All commands resolve <id> by session name, container name, or ID. Also: - Add resolveSession() helper to sandbox/index.ts - Export sessionsAction, cleanAction, printTable from sessions.tsx - Remove 'session' alias from sessionsCommand (now its own command)
CLI commands that print output and exit (session ps, info, logs, rm, stop, clean, and the sessions command) were hanging for seconds after output because background handles kept the event loop alive: 1. PostHog analytics client registers a beforeExit handler with a 2-second shutdown timeout 2. Cloud provider's syncSessionStatuses fires a background HTTP request to the Deno Deploy API Adding process.exit(0) after output is the standard CLI pattern and avoids waiting for these background operations to drain.
The Docker provider always starts containers detached at the Docker level and never streams output during create(). Follow mode (-f) was printing nothing because it assumed provider.create(detach=false) would stream output — but that never happened. Fix: after provider.create() returns, use provider.streamLogs() to stream the container output (same mechanism as 'session logs -f'), then re-fetch the session to get the exit code when the stream ends.
Register argument handlers with @bomb.sh/tab so that commands taking
a session ID (session rm/stop/attach/logs/info, and resume) complete
session names on TAB. Session names are loaded synchronously from:
1. Docker: Bun.spawnSync('docker ps') with ox.managed label filter
2. Cloud: synchronous SQLite read from the session database
Each completion shows the session name with its status and short ID
as the description (e.g., 'my-session Up 5 min (a7e618c9)').
The @bomb.sh/tab commander adapter only registers primary command names, so aliases like 'container' (for 'session'), 'delete' (for 'rm'), etc. were invisible to tab completion. Walk the commander tree after the adapter runs and register all aliases as mirror commands with the same options and argument handlers. This enables completion for all alias paths, e.g.: ox container info <TAB> -> session names ox container delete <TAB> -> session names ox s rm <TAB> -> session names
Two bugs in @bomb.sh/tab prevented positional argument completion when flags preceded the argument (e.g., 'ox session logs -f <TAB>'): 1. handlePositionalCompletion received raw args including flags, so its positional index calculation was off. Fix: strip options from args before counting. 2. parse() had an early return after detecting a boolean flag that skipped positional completion entirely. Fix: remove the early return so control falls through to handlePositionalCompletion. 3. The commander adapter marked ALL options as isBoolean=true, even value-taking ones like '--output <format>'. This caused stripOptions to not skip the value argument. Fix: detect '<' or '[' in flag syntax and pass a no-op handler to force isBoolean=false.
- Update Quick Start to show detached default and follow mode - Document --follow/-f, --interactive/-i, --agent-mode/-M flags - Add flag compatibility matrix - Document all session subcommands (ps, info, logs, stop, rm, attach, clean) - Reorganize CLI Reference into Root Command, Session Management, and Other Commands sections
console.error colors text red in many terminals. Progress messages (setup steps, confirmations) should use default terminal colors. Switch to printErr() which writes to stderr via process.stderr.write without any color formatting. Keep console.error only for actual error messages.
New subcommand 'ox session urls <id>' (alias: 'url') prints the proxied URLs for a session, one per line. Cloud sessions also print the external URL. Outputs to stdout for scriptability. Tab completion supported for the session argument.
'ox "task" -o json' prints the full session object (same as 'ox session info -o json') instead of just the bare session ID. This makes the CLI composable with jq/yq: ox -M interactive "start a server" -o json | jq -r '.portUrls[0].url' Supported formats: id (default, current behavior), json, yaml. Mutually exclusive with --follow and --interactive.
murrayju
left a comment
There was a problem hiding this comment.
Findings:
-
The root
oxcommand andox branchnow have two separate routing implementations, and they have already drifted in observable ways. On the root path, task-start flags like--follow,--agent-mode,--output, and--mountare accepted with no prompt and then ignored because the command falls through to the TUI prompt screen. On thebranchpath, the same invocation errors. That is both a correctness issue and a sign the shared task-start flow should be centralized. -
The new singular
ox session ...namespace is clearer, butox sessions cleanis still wired up even though the docs describeox sessionsas an alias forox session ps. That leaves the CLI in an in-between state where some management operations live under both singular and plural forms and others only under singular.
I reran the PR branch command-focused checks after installing dependencies in a clean worktree: ./bun run typecheck passed, and the targeted tests for completions/session workflow/session DB passed.
There was a problem hiding this comment.
Pull request overview
Redesigns the ox CLI execution model around “sessions” by defaulting to detached async runs (ID on stdout, progress on stderr), adding explicit terminal/agent mode flags, and introducing first-class session management commands.
Changes:
- Renames
submitMode→agentModeacross stores, config, sandbox types, and providers. - Updates root/branch CLI flow to support detached (default),
--follow, and--interactivemodes, plus--agent-modeand structured outputs. - Adds
ox sessionsubcommands, session resolution helper, and enhances shell completion/alias support (incl. patched@bomb.sh/tab).
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/stores/sessionWorkflowStore.ts | Carries autoSubmitAgentMode through TUI auto-submit path and passes agentMode to sandbox creation/resume. |
| src/stores/routerStore.ts | Updates router view typings from SubmitMode to AgentMode. |
| src/stores/promptSettingsStore.ts | Renames persisted mode setting to agentMode and updates setters/initialization. |
| src/services/sandbox/types.ts | Renames session/run mode type and session fields to AgentMode/agentMode. |
| src/services/sandbox/sessionDb.ts | Adds DB migration and persistence for agent_mode (renamed from submit_mode). |
| src/services/sandbox/index.ts | Exports AgentMode and adds resolveSession() helper. |
| src/services/sandbox/dockerProvider.ts | Maps Docker session field to agentMode and forwards it into container start. |
| src/services/sandbox/cloudProvider.ts | Persists/resumes cloud sessions with agentMode. |
| src/services/docker.ts | Renames docker label and session fields to agentMode, adds backward-compatible label fallback. |
| src/services/config.ts | Renames config key to agentMode and updates validation metadata. |
| src/index.ts | Updates root command routing/flag validation and adds session command registration. |
| src/components/PromptScreen.tsx | Renames UI state from submitMode to agentMode and updates mode switching/validation. |
| src/commands/sessions.tsx | Exposes reusable helpers/actions for new session command; updates aliases and output behavior. |
| src/commands/session.ts | Adds ox session command with rm/stop/attach/ps/logs/info/urls/clean subcommands. |
| src/commands/completion.ts | Adds session-id completions and mirrors commander aliases into tab completion registry. |
| src/commands/completion.test.ts | Updates completion expectations for new alias behavior and --follow flag. |
| src/commands/branch.ts | Implements new follow/interactive/agent-mode/output semantics and early flag validation. |
| patches/@bomb.sh%2Ftab@0.0.14.patch | Patches completion library behavior to better mirror commander options/args. |
| package.json | Registers patched dependency for @bomb.sh/tab@0.0.14. |
| bun.lock | Locks patched dependency mapping for @bomb.sh/tab@0.0.14. |
| README.md | Documents new default detached behavior, follow/interactive modes, agent modes, and session commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
… consistency - Remove `branch` subcommand entirely; root command is the sole task entry point - Remove `sessions clean` so `sessions` is a pure listing alias - Reject task-start flags (--follow, --agent-mode, --output, --mount) when no prompt given - Add .choices() validation to session ps/info --output options - Add disambiguation error to resolveSession() for cross-provider name collisions - Remove unused `detach` field from CreateSandboxOptions - Validate --tail argument in session logs (reject NaN/non-positive) - Replace require() with static import in completion.ts - Fix redundant choice listings in help output descriptions
murrayju
left a comment
There was a problem hiding this comment.
Remaining findings:
-
resolveSession()now throws on ambiguous names, but theox sessionsubcommands still only handlenull. That currently crashes the command path with anUnhandled promise rejectioninstead of returning a normal CLI error when a Docker session and cloud session share the same name. -
The new no-prompt guard still misses
ox -o id. Because the check only treats non-default output values as "set", the explicit default form opens the TUI instead of being rejected like the other task-start-only flags.
Verification:
./bun run typecheckpasses on the updated tip../bun test src/commands/completion.test.tspasses on the updated tip.- I also reproduced the ambiguity case locally by populating one Docker and one cloud session named
dup;ox session info dupcurrently goes through the crash handler rather than the regular CLI error path.
…gation - Use getOptionValueSource() to detect explicit --output flag vs default, so 'ox -o id' is rejected same as 'ox -o json' when no prompt given - Extract requireSession() helper in session.ts that catches thrown ambiguity errors from resolveSession(), surfacing them as normal CLI errors instead of unhandled rejections
Summary
ox "task"now creates an async session, prints the session ID to stdout, and exits immediately. Progress output goes to stderr for scriptability (SESSION=$(ox "task")).--follow/-f(stream agent output),--interactive/-i(launch TUI),--agent-mode/-M(async/interactive/plan)sessioncommand (alias:container) with subcommands:rm,stop,attach,ps,logs,info,cleansubmitMode→agentModethroughout codebase for consistency with--agent-modeflagCLI Examples
Flag Compatibility Matrix
--follow--follow--follow--interactive--interactive--interactive-f+-iChanges
SubmitMode/submitModetoAgentMode/agentModeacross 11 files--print/-pwith--follow/-f, add--agent-mode/-MagentMode === 'async'(notdetachflag)resolveSession()helper for session lookup by name/IDsessioncommand with 7 subcommands sharing infrastructure withsessionssessions.tsx(sessionsAction,cleanAction,printTable)