# `Raxol.AgentClientProtocol.Ctx`
[🔗](https://github.com/DROOdotFOO/raxol-agent/blob/raxol_agent_client_protocol-v0.1.0/packages/raxol_agent_client_protocol/lib/raxol/agent_client_protocol/ctx.ex#L1)

Ergonomic DX layer for agent handler code running **inside `session/prompt`
turn tasks** (the functions a `turn_runner` closure or one of its subagent
tasks actually calls). This module introduces **no new runtime semantics**:
every function here is a thin, documented wrapper over
`Raxol.AgentClientProtocol.Connection.request/4` (the agent->client
filesystem/terminal/permission requests) or
`Raxol.AgentClientProtocol.Session.post_update/2` /
`Session.request_permission/2` (the Session-owned turn primitives).

## Why this isn't built on `Connection.Ctx`

`Raxol.AgentClientProtocol.Connection.Ctx` is the per-DISPATCH
struct handed to the request/notification handler callback that answers
`session/prompt` itself (it carries `conn`, `reply_ref`, `rx_seq`, etc.).
It does **not** reach the turn task the handler spawns — `turn_runner` is
injected at `Session` start with a fixed `(session_pid, req) -> ...`
arity-2 shape (`session.ex` moduledoc, "Turn runner seam"), and a
subagent's task closure only ever receives what its parent closed over.
There is deliberately **one** ctx concept in this package
(`Connection.Ctx`); this module does not introduce a second,
competing one. Instead every function below takes the plain pid a turn
task actually has in hand — the `Connection` pid (closed over from the
original dispatch's `ctx.conn` when the app builds its `turn_runner` fun)
for the outbound protocol requests, or the `Session` pid (the task's own
`session` argument) for the two Session-owned primitives.

## What's here

  * `read_text_file/3,4`, `write_text_file/4,5` — `fs/read_text_file` /
    `fs/write_text_file`, sent via `Connection.request/4`. `request/4`
    blocks its caller, so it is for plain handler-task code; a process that
    must stay responsive uses `async_request/6` instead. A turn task is plain
    handler-task code, not the Session itself.
  * `create_terminal/3,4`, `terminal_output/3,4`, `wait_for_terminal_exit/3,4`,
    `kill_terminal/3,4`, `release_terminal/3,4` — the five `terminal/*`
    methods, same `Connection.request/4` wrapper.
  * `request_permission/3,4` — builds a `RequestPermissionRequest` and
    delegates to `Session.request_permission/2`. **Fail-closed, always**:
    the ONLY way to get an allow back is a
    literal `{:selected, %SelectedPermissionOutcome{}}` decoded from the
    client's reply; every other terminal (timeout, client error, decode
    failure, disconnect, a `session/cancel` racing the ask) resolves
    `{:ok, :cancelled}` — deny is the zero value everywhere. This wrapper
    adds no new fail-closed logic; it is documentation + ergonomics over
    the Session's existing guarantee.
  * `post_update/2` — delegates to `Session.post_update/2` verbatim (same
    arity, same contract): forwards a `session/update` for the live turn,
    surfacing `{:error, :turn_over}` for a straggler post after drain
    and `{:error, :empty_chunk}`
    for an empty-text `agent_message_chunk`/`agent_thought_chunk` (never
    sent to the wire — see `session.ex`'s `empty_chunk?/1`).

## Timeouts

`Connection.request/4`'s `timeout_ms` is `pos_integer()` — **not**
`:infinity` (only `async_request/6` accepts that). Every
function below takes a `:timeout` option (default `30_000`ms, except
`wait_for_terminal_exit/3,4` which defaults to 30 minutes since waiting
for a command to exit is expected to take a while); pass a larger
`:timeout` explicitly for a longer-running command rather than relying on
a library-invented unbounded wait.

# `create_terminal`

```elixir
@spec create_terminal(pid(), String.t(), String.t(), keyword()) ::
  {:ok, Raxol.AgentClientProtocol.Schema.ClientTypes.CreateTerminalResponse.t()}
  | {:error, term()}
```

`terminal/create` — create a terminal and execute `command`. `opts`:
`:args` ([String.t()]), `:env` (list of `{name, value}` string pairs or
pre-built `EnvVariable.t()` structs), `:cwd`, `:output_byte_limit`,
`:timeout` (default `30000`ms). Only sent if the client
declared the `terminal` capability.

# `kill_terminal`

```elixir
@spec kill_terminal(pid(), String.t(), String.t(), keyword()) ::
  {:ok, Raxol.AgentClientProtocol.Schema.ClientTypes.KillTerminalResponse.t()}
  | {:error, term()}
```

`terminal/kill` — kill the terminal's command without releasing the terminal. `opts`: `:timeout`.

# `post_update`

```elixir
@spec post_update(
  GenServer.server(),
  Raxol.AgentClientProtocol.Schema.LifecycleExtras.SessionNotification.t()
) :: :ok | {:error, :turn_over} | {:error, :empty_chunk}
```

Emit a `session/update` for the live turn — a verbatim pass-through to
`Session.post_update/2` (same arity, same contract): forwards `notification`
via the Connection's `notify/3` on the Session's own FIFO lane, returns
`{:error, :turn_over}` for a straggler post after the turn has drained,
and `{:error, :empty_chunk}` for an empty-text `agent_message_chunk`/
`agent_thought_chunk` — the streaming guard lives in `session.ex`
(`empty_chunk?/1`); this wrapper adds no logic of its own, only the
re-export so handler code has one module to `alias`.

# `read_text_file`

```elixir
@spec read_text_file(pid(), String.t(), String.t(), keyword()) ::
  {:ok, Raxol.AgentClientProtocol.Schema.ClientTypes.ReadTextFileResponse.t()}
  | {:error, term()}
```

`fs/read_text_file` — read a text file on the client side. `opts`:
`:line` (1-based start line), `:limit` (max lines), `:timeout`
(default `30000`ms). Only sent if the client declared the
`fs.readTextFile` capability (the client answers `-32601` otherwise —
MethodTable's capability gate, unrelated to this wrapper).

# `release_terminal`

```elixir
@spec release_terminal(pid(), String.t(), String.t(), keyword()) ::
  {:ok,
   Raxol.AgentClientProtocol.Schema.ClientTypes.ReleaseTerminalResponse.t()}
  | {:error, term()}
```

`terminal/release` — release a terminal and free its resources. `opts`: `:timeout`.

# `request_permission`

```elixir
@spec request_permission(
  GenServer.server(),
  String.t(),
  Raxol.AgentClientProtocol.Schema.ToolCallUpdate.t(),
  [Raxol.AgentClientProtocol.Schema.ClientTypes.PermissionOption.t()]
) ::
  {:ok,
   {:selected,
    Raxol.AgentClientProtocol.Schema.ClientTypes.SelectedPermissionOutcome.t()}}
  | {:ok, :cancelled}
```

Ask the client for permission to perform `tool_call` (`session/request_permission`),
offering `options`. Delegates to `Session.request_permission/2` — `session`
is the turn's own `Session` pid (the task's `session` argument), NOT the
Connection. **Fail-closed**: returns
`{:ok, {:selected, %SelectedPermissionOutcome{}}}` ONLY on a decoded
selected outcome; every other terminal — timeout, client error reply,
undecodable reply, transport/connection death, or a `session/cancel`
racing the ask — returns `{:ok, :cancelled}`. There is no third outcome
and no exception path: deny is the zero value everywhere. The Session
arms no timer of its own for this wait; `Connection.async_request/6`
is the single timeout authority, configured per-connection via
the Session's `:permission_timeout` (default 600s).

# `terminal_output`

```elixir
@spec terminal_output(pid(), String.t(), String.t(), keyword()) ::
  {:ok, Raxol.AgentClientProtocol.Schema.ClientTypes.TerminalOutputResponse.t()}
  | {:error, term()}
```

`terminal/output` — the output captured so far, and exit status if the command has completed. `opts`: `:timeout`.

# `wait_for_terminal_exit`

```elixir
@spec wait_for_terminal_exit(pid(), String.t(), String.t(), keyword()) ::
  {:ok,
   Raxol.AgentClientProtocol.Schema.ClientTypes.WaitForTerminalExitResponse.t()}
  | {:error, term()}
```

`terminal/wait_for_exit` — block until the terminal command exits. `opts`:
`:timeout` (default `1800000`ms = 30 minutes, since
waiting for a command is expected to take a while; `request/4` does not
accept `:infinity` — pass an explicit larger `:timeout` for a
longer-running command instead).

# `write_text_file`

```elixir
@spec write_text_file(pid(), String.t(), String.t(), String.t(), keyword()) ::
  {:ok, Raxol.AgentClientProtocol.Schema.ClientTypes.WriteTextFileResponse.t()}
  | {:error, term()}
```

`fs/write_text_file` — write `content` to a text file on the client side.
`opts`: `:timeout` (default `30000`ms). Only sent if the
client declared the `fs.writeTextFile` capability.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
