Agent Shells
Claude and Codex sessions open as editor tabs, leaving the terminal panel
available for build output and utility commands. The Agent Shells Activity Bar
view keeps every live session reachable without a naming prompt.
Requirements
Claude and Codex run inside VS Code's own persistent editor terminals, so no
external process manager is required. The durable session backend supports
macOS and Linux; it does not provide a native Windows backend. Scrollback
restored after a window reload is capped by
terminal.integrated.persistentSessionScrollback (100 lines by default); raise
that setting to keep more history across reloads.
Usage
Open Agent Shells from the Activity Bar and select /usage to open its
terminal-style editor tab. The editor tab uses a dashboard meter icon. Its
horizontal page navigation places /usage, the active OVERVIEW page, and
muted non-interactive LIMITS, COSTS, and MODELS placeholders vertically
centered beside the donut. The flat terminal screen uses aligned text,
character meters, a lightweight animated ASCII donut, and the canonical Claude
and Codex provider symbols. It reads usage recorded by Claude and Codex on this
machine and refreshes every minute. Both provider columns reserve the same row
order: tokens, USD API list-price estimate, provider account value, 5h limit,
1w limit, and provider-scoped limits. Claude uses its account-value row for
extra-usage spend in the source currency. Codex uses it for a USD credit
balance, unlimited, or n/a when no credit pool exists. Before each read,
the panel asks the configured status-line producer to refresh any expired
account data. That producer owns both provider protocols, the lock, and the
15-minute lease, so the status line and /usage share one request path and one
snapshot. Fresh zero remains visible even when the provider reports no next
reset time. Missing current values keep their reserved row and render an
explicit unavailable or refresh-failed state instead of an expired percentage.
Nonzero usage without a configured list price remains counted in tokens and
excluded from the estimate, without adding model detail to the overview. The
estimate is not a subscription charge, invoice total, or authenticated billing
balance. Transcript stores remain read-only; only the shared status-line
producer updates account state.
Select the Claude symbol to launch claude immediately, or select the Codex
symbol to launch codex immediately. Each command starts in the active file's
workspace folder. The first workspace folder is used when the active editor
belongs elsewhere. The provider runs directly in a native persistent terminal
editor tab through a small start-or-resume runner script kept under extension
global storage. Reloading the VS Code window reconnects the same live provider
process: VS Code restores the terminal tabs itself, restored rows appear in the
tree immediately with their persisted titles, and Agent Shells re-binds each
row to its terminal in the background through the shell process identifier the
runner recorded. Quitting VS Code ends provider compute; the next launch reruns
the runner, which resumes the recorded conversation in its original working
directory once the session identity is known and starts fresh otherwise.
Activity, attention, and title synchronization start as soon as a restored row
is registered, and restoration never reveals or refocuses a tab. Selecting a
row whose terminal is not bound yet never launches a second conversation while
the recorded process is alive: the reveal waits for the re-binding and opens a
fresh runner terminal only once the process is confirmed gone; a binding window
that closes with the process still alive leaves the row waiting and reports it
in the status bar. A click on an unbound row shows a short reconnecting status
message and probes the recorded process, so a session whose terminal missed the
reload snapshot resumes within seconds instead of holding the row inert for the
whole binding window. Every restoration and terminal-binding step writes one
structured record per session to the Agent Shells output log. Recovery
records are scoped to the current VS Code workspace.
Select the history action to load previous non-active Claude and Codex
sessions. The picker opens without a highlighted row, reads provider metadata
and the first completed prompt, limits results to the open workspace, and
resumes the selected session from its original working directory. Codex
sessions attributed to Agent Shells remain visible across workspaces when /
is their recorded working directory. Unrelated sessions outside the workspace
remain excluded. When two windows try to resume the same session within the
same brief launch window, a per-session reservation lets only one of those
overlapping attempts proceed, and it is released as soon as the terminal is
dispatched. It deduplicates concurrent launches only: a window that resumes the
session after another window has already launched it is not blocked, so the
same session can still end up in two terminals. While a reservation is held, it
is keyed to the launching process and cleared if that process exits or stalls
past a several-minute backstop. Live sessions prefix their title with compact
recency such as (1.0h) once the latest persisted provider event is at least
one displayed hour old. History uses the compact Last activity 0.4h ago
wording at every age. New provider output resets this age even when no new user
message exists. Its trash action hides an unwanted entry from later history
views without deleting provider data. Both providers use the same persistent
display-metadata store. Existing title-only records use the first-prompt
preview as their description. Longer previews end with .... History never
makes a model call.
History represents user sessions rather than every provider transcript. Every
Claude SDK entrypoint, Codex exec session, child agent, and promptless record
is excluded because it cannot be resumed as a user conversation. Claude IDE
context blocks such as ide_opened_file, ide_selection, and
ide_diagnostics are removed from prompt descriptions while human text in the
same record remains intact. Slash commands retain their command name and
arguments without Claude's XML envelope.
Live sessions start with provider-owned names such as Claude Session 1 and
Codex Session 2, with the newest session at the top of the Agent Shells tree.
Each terminal editor receives its provider icon when it is created; a tab
reconnected after a window reload can come back with the default terminal icon
instead, because VS Code has no way to re-apply an icon to a live terminal (the
tree row keeps its provider icon either way). A native spinner replaces the
Agent Shells row icon while either provider is processing a turn. Claude keeps
the spinner while any main-session subagent remains active. An account icon
takes precedence while either provider is blocked on a question, permission
approval, MCP elicitation, or Codex safety-buffering choice, and the provider
icon returns when the request resolves. Claude state changes come from
side-effect-only lifecycle hooks and persisted provider events. Codex attention
icons use the local app-server observer by default; open sessions reconnect
after a socket interruption, and Codex terminals still open with file-backed
activity monitoring if observation is unavailable. By default, Ctrl+C in a
focused managed terminal clears stale spinner, attention, and subagent state,
then forwards the interrupt to the provider. It never removes a title, because
interrupting is a normal way to steer an agent mid-turn and a committed title
must survive it. Explicit terminal skip-shell settings remain authoritative. A
later provider event restores its current state. POSIX hosts run Claude and
Codex through the login shell via the runner script. Provider exit closes the
editor terminal and removes its tree row. The first active Claude or Codex
prompt previews a bounded title once in the row and terminal without writing
recovery or display metadata. Codex and Claude prompts start one semantic title
request from the submitted first prompt, and its result persists immediately as
the final title: reloads, interrupts, and later completed prompts read it back
instead of regenerating, so the name a session gets is the name it keeps. Only
a manual rename or a provider-sourced title replaces it. A prompt-text fallback
appears only when generation fails, and the first completed prompt may then
upgrade that fallback. The wheel scrolls real terminal scrollback for every
provider, and text selection with copy and paste stays native VS Code behavior.
The terminal turns wheel input into arrow keys whenever it shows an alternate
buffer, and arrow keys recall and can resubmit previous provider messages, so
Codex launches with tui.alternate_screen=never and runs inline like Claude,
keeping conversation history in the normal buffer. An attached image does not
block the later text prompt from naming the session. Several empty Codex
terminals can coexist. The extension records ownership before terminal
creation, then associates a provider thread with the closest eligible unmatched
launch inside a narrow startup scheduling boundary. A claimed thread identifier
cannot move to another row. Closing an owner before its deferred thread
persists reserves that thread for history instead of transferring its title to
another live row. A failed metadata process attempt receives one delayed retry
within a 60-second attempt boundary. Exhausted attempts retain the fallback and
write the provider, full session identifier, operation, failure category,
attempt count, safe output lengths, fallback outcome, and persistence outcome
to the Agent Shells output log without prompt or generated text. Transient
Claude discovery failures log the same qualified identity and retry instead of
terminating live attachment. A later extension activation or explicit resume
can run one fresh enrichment cycle; polling cannot. Both providers store
normalized title, description, and new-record provenance together by provider
and session identifier. Legacy records remain authoritative. Once live
attachment finds a transcript, pending title polling rereads that file directly
instead of rescanning the provider history. Codex terminal-title updates are
disabled for sessions launched by the extension, preventing the workspace
directory from replacing the session name. The pencil action persists a
user-supplied name and updates the Agent Shells row and terminal editor tab. A
rename made before the first completed prompt keeps polling until a real prompt
description can be stored. Activating a managed terminal editor tab selects its
matching Agent Shells row without moving focus. Option+Command+C copies the
selected provider session identifier while the Agent Shells view is focused, or
the active provider session identifier while a managed terminal is focused.
Codex identity attaches as soon as the owned provider record is known,
independently of whether its first task can drive a title. A missing selection
or provider identity reports the unresolved state without changing the
clipboard. Close the terminal to remove the session from the tree, or use the X
action beside the session to close it from the view. Either explicit close ends
the session and removes its recovery record and runner state. VS Code window
shutdown and reload detach instead, so the session record survives for
restoration.
Sessions remain in the Claude and Codex stores after their terminal closes. The
extension treats those stores as read-only and stores display metadata and
hidden-history markers in VS Code extension state.
Each activation performs one bounded local repair pass over missing display
metadata after restored terminals either reach first paint or report a bounded
failure. Manual and unclassified legacy cache values remain authoritative.
Metadata derived from a provably incomplete first turn is removed. Regular
Claude sessions use completed-turn ai-title records, and every other prompted
missing record receives a deterministic local fallback. The repair pass never
invokes Luna. Resume repeats the same local guarantee before creating the live
surface, so a failed earlier write cannot recreate an unnamed row.
Settings
agentShells.experimentalCodexAttention is enabled by default. It prepares the
local Codex app server and waits for its WebSocket observer before launching
the terminal against the same endpoint. Pending approvals and user-input
requests then replace the working spinner with the account icon until the
request resolves. If preparation fails, the terminal launches as an ordinary
Codex session without attention observation. Turning the setting off always
uses that ordinary launch path.
Architecture
src/extension.ts composes the extension lifecycle and registers the owned
controller, tree, provider clients, usage panel, and disposables.
src/extensionRuntime.ts keeps storage paths, setting lookup, attention
wiring, and background startup out of that composition flow.
src/sessions/ owns user workflows and provider-neutral live state.
SessionController coordinates start, resume, reveal, rename, copy, interrupt,
close, restore, and history selection through SessionSurface. The
durableSession.ts boundary persists recovery records and the per-session
start-or-resume runner state owned by sessionRunner.ts, behind
nativeTerminal.ts, which keeps vscode.Terminal out of the controller.
History discovery, ownership, resume locking, display metadata, activity, and
tree presentation remain separate modules under the same domain.
src/providers/provider.ts defines the composed launch, history, identity,
activity, attention, title, and presentation capabilities used by stable
session orchestration. src/providers/registry.ts is the only composition
point that imports concrete providers. src/providers/claude/ and
src/providers/codex/ own their formats and capability implementations without
cross-importing or importing session orchestration. Dependency rules and a
provider-locality syntax check reject concrete provider imports and
provider-name branches across stable source outside provider-owned and usage
boundaries. A synthetic third provider enters through the production registry,
so a new provider is one implementation directory and one registry entry rather
than a distributed conditional change.
Codex WebSocket framing remains separate from application server lifecycle.
src/usage/ reads provider stores without modifying them, asks the shared
status-line producer to refresh expired account data, prices decoded transcript
usage, renders deterministic markup, and owns the webview panel. Browser code
and styles live under resources/usage/ and load through the webview resource
boundary.
History and activation repair never invoke semantic generation. Metadata
synchronization owns fallback precedence, provenance-aware replacement, and
workflow-level call guards, while the generator owns its bounded process
attempt and one delayed retry. Codex attention aggregates parent and descendant
thread waits before projecting one state onto the managed parent row. Usage
collection imports no provider credentials or protocols; the status-line
producer deduplicates account refreshes under its existing lock and lease.
Development
Run commands from the agent-shells directory:
npm run quality:static checks typed lint rules, formatting, dependency
boundaries, cycles, dead files, unused exports, and unused dependencies.
npm run typecheck checks strict TypeScript source and the browser dashboard
JavaScript boundary.
npm run test:unit compiles the extension and runs every nested unit test.
npm run test:extension runs activation, terminal, tree, and webview smoke
checks in the Extension Development Host.
npm run package:validate compiles and inspects the VSIX inventory.
npm run audit:dependencies rejects dependency vulnerabilities at moderate
severity or higher.
npm run test:coverage writes a diagnostic coverage report without imposing
a repository-wide percentage target.
npm run check runs the complete local quality, type, unit, Extension Host,
package, and dependency boundary.
The monorepo root .pre-commit-config.yaml applies file hygiene first, then
staged ESLint and Prettier checks, architecture analysis, strict typing, unit
tests, and dependency auditing for package changes. Rule exceptions stay at the
narrowest verified file or test boundary and carry their invariant beside the
configuration.