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
30 changes: 30 additions & 0 deletions src/commands/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ export function registerSession(program: Command): void {
if (opts.config) req.config_id = opts.config
if (opts.configFile) req.config = readConfigFile(opts.configFile)
if (opts.template) req.template_id = opts.template
// The repo we're standing in (origin remote), sent unconditionally —
// with no config source it picks the repo rung of the server's
// defaults ladder, and either way the server merges it into the
// sandbox checkout set. Outside a git repo (or with no usable
// remote) nothing is sent.
const contextRepo = repoFromCwd(process.cwd())
if (contextRepo) req.repository = contextRepo
// Sugar flags (--model, --repo, --cpu, ...) and the raw
// --config-override are merged into one structured override, applied
// onto the chosen (or default) config and re-validated server-side.
Expand All @@ -217,7 +224,30 @@ export function registerSession(program: Command): void {
const api = new ApiClient()
const session = await api.startAgentSession(req)

// Say which agent the server picked when it came from the defaults
// ladder, so a bare `agent` never silently runs an unexpected config.
if (!opts.json && session.resolved_config_name) {
if (session.resolution_source === 'repo_default') {
console.log(`using config "${session.resolved_config_name}" (repo default)`)
} else if (session.resolution_source === 'account_default') {
console.log(`using config "${session.resolved_config_name}" (account default)`)
}
}

if (opts.connect) {
// A non-interactive config refuses the stream/messages surface, so
// a connect would fail; degrade to watching the output instead.
const ellipsisBlock = (session.agent_config as Record<string, unknown> | undefined)
?.ellipsis as { interactive?: boolean } | undefined
if (ellipsisBlock?.interactive === false) {
console.log(
'this agent is not interactive; watching output instead of connecting',
)
console.log(`✓ started session ${session.id}`)
await printSessionUrl(api, session.id)
await watchSessionStreaming(api, session.id, FALLBACK_POLL_INTERVAL_SECONDS, false)
return
}
await startConnect(session)
return
}
Expand Down
12 changes: 12 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export interface AgentSession {
cost_fee: number
tokens_total: number
metadata: Record<string, string>
// Present on the POST /v1/sessions response only (StartAgentSessionResponse):
// which config the session runs under and which rung of the defaults ladder
// chose it (null when an explicit config/template bypassed resolution).
resolved_config_name?: string | null
resolution_source?: 'repo_default' | 'account_default' | 'none' | null
[key: string]: unknown
}

Expand Down Expand Up @@ -172,6 +177,13 @@ export interface StartAgentSessionRequest {
config?: AgentConfig
template_id?: string
handoff?: HandoffAgentSessionParams
// The "owner/name" repository the CLI is standing in (origin remote). With
// no explicit config source it selects the repo rung of the server's
// default-config ladder (repo default -> account default -> bare config),
// and it is always merged into the sandbox repository set (cloned at the
// default branch), even alongside --config. Unknown/foreign repos are
// ignored server-side.
repository?: string
// No `source`: the server derives a session's provenance from the credential
// (a user token => `cli`), so it can't be spoofed by the request body.
metadata?: Record<string, string>
Expand Down