Documentation
¶
Overview ¶
Package codexappserver speaks the Codex app-server JSON-RPC protocol to a private stdio child process.
Codex refuses to run a non-managed lifecycle hook until the exact hook definition has been reviewed and trusted, recording that decision as a content hash under `hooks.state` in the user's `config.toml`. A hook that has never been trusted is parsed, listed, and silently skipped, so an installer that only writes `hooks.json` reports success for a binding that can never fire.
The hash is derived from a normalized hook identity, not from the bytes on disk — Codex clamps a SessionEnd timeout before hashing, for one — so reproducing it locally would be wrong in ways that only surface as silence. This package asks Codex for the value instead: `hooks/list` returns each hook's `key`, `currentHash`, and `trustStatus`, and `config/batchWrite` persists trust through Codex's own writer under an optimistic-concurrency version. Nothing here parses or rewrites `config.toml`.
The app-server CLI surface is marked experimental. Every call here is best-effort by contract: callers degrade to reporting an untrusted binding rather than failing setup (ADR-051).
Index ¶
- Constants
- Variables
- func Home(override string) (string, error)
- func Locate() (string, error)
- type Client
- func (c *Client) Close() error
- func (c *Client) ListHooks(ctx context.Context, cwds []string) ([]Hook, error)
- func (c *Client) ListThreads(ctx context.Context, cwd string, limit int) ([]Thread, error)
- func (c *Client) ThreadReads(ctx context.Context, id string) (cwd string, reads []ThreadRead, err error)
- func (c *Client) Trust(ctx context.Context, edits []TrustEdit, expectedVersion string) error
- func (c *Client) UserConfigVersion(ctx context.Context) (string, error)
- type Hook
- type Options
- type Thread
- type ThreadRead
- type TrustEdit
Constants ¶
const ( TrustManaged = "managed" TrustTrusted = "trusted" TrustUntrusted = "untrusted" TrustModified = "modified" )
Trust states reported by hooks/list.
Variables ¶
var ErrCodexNotFound = errors.New("no Codex executable found")
ErrCodexNotFound reports that no Codex executable could be located. It is an ordinary condition on a machine that has never installed Codex, not a fault.
Functions ¶
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client owns one `codex app-server` child process. It is not safe for concurrent use; calls are serialized by the caller or by mu.
func Dial ¶
Dial starts a private app-server and completes the initialize handshake.
This deliberately spawns its own stdio child rather than attaching to a shared daemon: the Codex desktop application runs its app-server as a private stdio child with no socket to join, and the shared daemon requires the standalone Codex installer. A private child observes no live runtime state; it can inspect persisted thread metadata and read or write configuration.
func (*Client) ListHooks ¶
ListHooks returns every hook Codex resolves for the given working directories. An empty cwds list asks Codex for its own default.
func (*Client) ListThreads ¶
ListThreads returns at most limit unarchived threads whose recorded cwd exactly matches cwd, newest update first. Callers ask for two because that is sufficient to distinguish a unique candidate from an ambiguous checkout.
func (*Client) ThreadReads ¶
func (c *Client) ThreadReads(ctx context.Context, id string) (cwd string, reads []ThreadRead, err error)
ThreadReads returns the working directory of a stored Codex task and the file reads its own classifier attributed to completed commands.
This reads a task without resuming or taking ownership of it: `thread/read` is documented as reading a stored task by id, and a separate app-server process observing a task the desktop application is still running reports it as `notLoaded`. Overgent never issues `thread/start`, `thread/resume`, `turn/start`, or any approval, so it observes the member's session without participating in it (ADR-051, ADR-052).
The decoded shape deliberately has no field for `command` or `aggregatedOutput`. Those cross the wire from Codex and are dropped during decoding rather than held and discarded later, so a raw command string or captured output never reaches an Overgent structure at all.
func (*Client) Trust ¶
Trust records the given hooks as trusted in the user's config.toml through Codex's own configuration writer.
Every edit is a narrow upsert of a single `hooks.state."<key>".trusted_hash` value. Overgent never serializes the surrounding document, so a concurrent write by the Codex desktop application cannot be clobbered by this call, and expectedVersion turns a lost update into a returned error rather than silent damage.
func (*Client) UserConfigVersion ¶
UserConfigVersion returns the version of the user config layer, used as the optimistic-concurrency token for a write. An empty result means the layer was not reported and the write must proceed without a compare-and-swap.
type Hook ¶
type Hook struct {
Key string `json:"key"`
EventName string `json:"eventName"`
HandlerType string `json:"handlerType"`
Command string `json:"command"`
Source string `json:"source"`
SourcePath string `json:"sourcePath"`
IsManaged bool `json:"isManaged"`
Enabled bool `json:"enabled"`
CurrentHash string `json:"currentHash"`
TrustStatus string `json:"trustStatus"`
}
Hook is one entry from hooks/list. Only the fields Overgent acts on are decoded; the protocol carries more and may add more.
type Options ¶
type Options struct {
Executable string
CodexHome string
// ClientVersion is reported to Codex during initialize.
ClientVersion string
}
Options configure a Dial. Executable and CodexHome are resolved when empty.
type Thread ¶
type Thread struct {
ID string `json:"id"`
CWD string `json:"cwd"`
UpdatedAt int64 `json:"updatedAt"`
}
Thread is the identity and recency metadata Overgent needs from `thread/list`. Conversation previews, paths to rollout files, and other content-bearing fields are intentionally not decoded.
type ThreadRead ¶
type ThreadRead struct {
// ItemID is the app-server item this read came from. Callers deduplicate on
// it so re-reading a thread republishes nothing.
ItemID string
// Path is absolute, as Codex reports it. It is meaningless until the caller
// has checked it against a registered repository root.
Path string
}
ThreadRead is one file read that Codex's own classifier attributed to a command it ran. It is vendor-inferred evidence, not an observation of the filesystem: OpenAI describes command actions as a best-effort understanding of what a command will do, and a compound command that genuinely reads files can still classify as `unknown` (ADR-052).