Skip to content

feat: redesign CLI session modes and add session management commands#105

Merged
murrayju merged 22 commits intomainfrom
cli-session-modes
Mar 16, 2026
Merged

feat: redesign CLI session modes and add session management commands#105
murrayju merged 22 commits intomainfrom
cli-session-modes

Conversation

@murrayju
Copy link
Member

Summary

  • Default CLI behavior changed: 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")).
  • New flags: --follow/-f (stream agent output), --interactive/-i (launch TUI), --agent-mode/-M (async/interactive/plan)
  • New session command (alias: container) with subcommands: rm, stop, attach, ps, logs, info, clean
  • Internal rename: submitModeagentMode throughout codebase for consistency with --agent-mode flag

CLI Examples

ox "some task"                    # detached: print session ID, exit
ox -f "some task"                 # follow: stream output, exit with agent code
ox -i "some task"                 # interactive: launch TUI with auto-submit
ox -M plan "some task"            # start plan-mode agent, print ID, exit
ox session ps                     # list sessions
ox session info <name>            # show session details
ox session logs -f <name>         # follow session logs
ox session stop <name>            # stop a session
ox session rm <name>              # remove a session

Flag Compatibility Matrix

Terminal Mode Agent Mode Valid?
detached (default) async (default) Yes
detached interactive Yes
detached plan Yes
--follow async Yes
--follow interactive Error
--follow plan Error
--interactive interactive (implied) Yes
--interactive async Yes
--interactive plan Yes
-f + -i Error

Changes

  • Rename SubmitMode/submitMode to AgentMode/agentMode across 11 files
  • Replace --print/-p with --follow/-f, add --agent-mode/-M
  • Gate PR instructions on agentMode === 'async' (not detach flag)
  • Add resolveSession() helper for session lookup by name/ID
  • Add session command with 7 subcommands sharing infrastructure with sessions
  • Export shared utilities from sessions.tsx (sessionsAction, cleanAction, printTable)

murrayju added 17 commits March 13, 2026 18:24
… --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.
Copy link
Member Author

@murrayju murrayju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings:

  1. The root ox command and ox branch now 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 --mount are accepted with no prompt and then ignored because the command falls through to the TUI prompt screen. On the branch path, the same invocation errors. That is both a correctness issue and a sign the shared task-start flow should be centralized.

  2. The new singular ox session ... namespace is clearer, but ox sessions clean is still wired up even though the docs describe ox sessions as an alias for ox 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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 submitModeagentMode across stores, config, sandbox types, and providers.
  • Updates root/branch CLI flow to support detached (default), --follow, and --interactive modes, plus --agent-mode and structured outputs.
  • Adds ox session subcommands, 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
Copy link
Member Author

@murrayju murrayju left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining findings:

  1. resolveSession() now throws on ambiguous names, but the ox session subcommands still only handle null. That currently crashes the command path with an Unhandled promise rejection instead of returning a normal CLI error when a Docker session and cloud session share the same name.

  2. 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 typecheck passes on the updated tip.
  • ./bun test src/commands/completion.test.ts passes on the updated tip.
  • I also reproduced the ambiguity case locally by populating one Docker and one cloud session named dup; ox session info dup currently 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
@murrayju murrayju merged commit 221b6f6 into main Mar 16, 2026
2 checks passed
@murrayju murrayju deleted the cli-session-modes branch March 16, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants