Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 4 additions & 33 deletions src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
StreamUnavailableError,
type StreamFrame,
} from '../lib/ws'
import { attachTerminal, describeTerminalClose, isCleanClose } from '../lib/terminal'
import type { AgentSession } from '../lib/types'

// `agent session connect [sessionId]` — the terminal window into a cloud
Expand Down Expand Up @@ -73,48 +72,20 @@ export function registerConnect(session: Command): void {
'Connect to a cloud session: view the conversation, follow it live, and send messages',
)
.option('--no-backlog', 'skip printing the stored transcript on open')
.option(
'--raw',
"attach a raw PTY to the agent's live terminal (the pixel-perfect TUI; needs an interactive terminal)",
)
.addHelpText(
'after',
`\nDefault (message mode): render the conversation, follow it live, and send lines
through the session inbox — single-writer-safe and usable headless / inside a
sandbox. --raw takes over your terminal and drives the agent's live TUI
directly (detach with ${'Ctrl-]'}); it needs a running sandbox and a real TTY.`,
`\nMessage mode: render the conversation, follow it live, and send lines through
the session inbox — single-writer-safe and usable headless / inside a sandbox.`,
)
.action(async (sessionId: string | undefined, opts: { backlog: boolean; raw?: boolean }) => {
.action(async (sessionId: string | undefined, opts: { backlog: boolean }) => {
await runAction(async () => {
const id = resolveConnectSessionId(sessionId)
if (opts.raw) {
await runConnectRaw(id)
return
}
await runConnect(id, opts.backlog)
})
})
}

// The raw-PTY attach: take over the terminal and bridge it to the session's
// live ttyd (documents/eng/INTERACTIVE_SESSIONS.md §5). Shared by `connect
// --raw` and `session start --connect`. Assumes the sandbox is live; the
// backend closes with a curated reason (surfaced below) when it isn't.
export async function runConnectRaw(sessionId: string): Promise<void> {
const token = requireToken()
const wsBase = resolveWsBase(resolveApiBase())
const result = await attachTerminal({ token, sessionId, wsBase })
// The bridge has restored the terminal; report on a fresh line.
process.stdout.write('\r\n')
if (isCleanClose(result.code)) {
console.log('detached — the session keeps running (reconnect with the same command)')
return
}
console.log(`✗ ${describeTerminalClose(result.code, result.reason)}`)
process.exitCode = 1
}

async function runConnect(sessionId: string, backlog: boolean): Promise<void> {
export async function runConnect(sessionId: string, backlog: boolean): Promise<void> {
const api = new ApiClient()
const token = requireToken()
const wsBase = resolveWsBase(resolveApiBase())
Expand Down
17 changes: 9 additions & 8 deletions src/commands/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
type SyncOutcome,
} from '../lib/laptop'
import { openBrowser } from '../lib/auth'
import { registerConnect, runConnectRaw } from './connect'
import { registerConnect, runConnect } from './connect'
import { formatStepLine, oneLine, stepText } from '../lib/steps'
import { ApiError } from '../lib/api'
import { resolveToken } from '../lib/config'
Expand Down Expand Up @@ -114,7 +114,7 @@ export function registerSession(program: Command): void {
.option('-w, --watch', 'stream the session live until it reaches a terminal status')
.option(
'--connect',
"after starting, wait for the sandbox and attach a raw PTY to the agent's live terminal (needs an interactive terminal)",
'after starting, wait for the sandbox and connect: view the conversation, follow it live, and send messages',
)
.option('--json', 'output raw JSON')
.action(
Expand Down Expand Up @@ -663,10 +663,11 @@ export function registerSession(program: Command): void {
)
}

// `start --connect`: wait for the session's sandbox to come live, then attach a
// raw PTY (the same bridge as `session connect --raw`). A terminal status before
// `start --connect`: wait for the session's sandbox to come live, then drop into
// the semantic connect (render the conversation, follow it live, send messages
// through the inbox — the same as `session connect`). A terminal status before
// RUNNING means the session never got a sandbox (e.g. a preflight/budget gate),
// so there is nothing to attach to.
// so there is nothing to connect to.
const CONNECT_READY_TIMEOUT_SECONDS = 120

export async function startConnect(api: ApiClient, sessionId: string): Promise<void> {
Expand All @@ -683,8 +684,8 @@ export async function startConnect(api: ApiClient, sessionId: string): Promise<v
}
if (Date.now() > deadline) {
console.log(
`\ntimed out waiting for the sandbox; once it is running, attach with:\n` +
` agent session connect --raw ${sessionId}`,
`\ntimed out waiting for the sandbox; once it is running, connect with:\n` +
` agent session connect ${sessionId}`,
)
process.exitCode = 1
return
Expand All @@ -693,7 +694,7 @@ export async function startConnect(api: ApiClient, sessionId: string): Promise<v
await sleep(1000)
}
process.stdout.write(' ready\n')
await runConnectRaw(sessionId)
await runConnect(sessionId, true)
}

// `--watch` entry point: stream the session's output live over WebSocket, and
Expand Down
230 changes: 0 additions & 230 deletions src/lib/terminal.ts

This file was deleted.

Loading
Loading