agent

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 28, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package agent watches the JSONL transcripts that Codex and Claude Code write while their TUI runs, and projects those files as normalized events. The PTY byte stream stays the source of truth; this is a side channel for clients (notably Web) that want a structured view of the same process.

Index

Constants

View Source
const (
	// BindEnvSession marks the Warren session owning the CLI process. Hooks
	// and tool subprocesses see it, but only the managed hook uses it.
	BindEnvSession = "WARREN_SESSION_ID"
	// BindEnvFile points the managed hook at the file it must write with the
	// CLI session ID and transcript path.
	BindEnvFile = "WARREN_BIND_FILE"
	// BindEnvKind names the agent kind (codex or claude) for the hook.
	BindEnvKind = "WARREN_AGENT_KIND"
	// BindEnvState points the managed hook at the file it must update when the
	// CLI session changes lifecycle, so the daemon can update the status overlay.
	BindEnvState = "WARREN_STATE_FILE"
	// CodexSessionID and CodexThreadID are launcher-provided identity hints.
	// An interactive shell can inherit them from its parent Codex process; an
	// independently-created Warren session must not reuse that conversation.
	BindEnvCodexSessionID = "CODEX_SESSION_ID"
	BindEnvCodexThreadID  = "CODEX_THREAD_ID"
)
View Source
const (
	// DefaultReadRecent bounds a command result so an external agent does not
	// accidentally ingest an entire long-running conversation.
	DefaultReadRecent = 20
	// DefaultReadContentLimit is measured in Unicode code points, not bytes.
	DefaultReadContentLimit = 2000
	// MaxReadActivities bounds an unbounded (--all) read. Callers that need a
	// smaller response should set Recent explicitly.
	MaxReadActivities = 100000
)

Variables

This section is empty.

Functions

func BindDir

func BindDir() string

BindDir is the directory holding per-session bindings.

func BindEnvironment

func BindEnvironment(warrenSessionID, kind string) []string

BindEnvironment returns the runtime environment entries a session needs so the managed hook can report an agent CLI's own session ID to Warren.

func BindPath

func BindPath(warrenSessionID string) string

BindPath is the per-session file the managed hook writes.

func ClaudeConfigDir

func ClaudeConfigDir() string

ClaudeConfigDir returns the directory Claude reads settings from, honoring CLAUDE_CONFIG_DIR just like the CLI itself.

func ClaudeProjectsRoot

func ClaudeProjectsRoot() string

ClaudeProjectsRoot returns the directory Claude writes transcripts into, honoring CLAUDE_CONFIG_DIR just like the CLI itself.

func ClaudeTranscriptPath

func ClaudeTranscriptPath(claudeRoot, workspacePath, sessionID string) string

ClaudeTranscriptPath computes the transcript file Claude writes for a given session ID in a given working directory.

func CodexHome

func CodexHome() string

CodexHome returns the Codex configuration directory the daemon's children inherit, honoring CODEX_HOME just like the CLI itself.

func EnsureClaudeBindHook

func EnsureClaudeBindHook(claudeConfigDir string) (changed bool, err error)

EnsureClaudeBindHook installs the same two hooks into Claude's user settings file, preserving every existing entry.

func EnsureCodexBindHook

func EnsureCodexBindHook(codexHome string) (changed bool, err error)

EnsureCodexBindHook installs the Warren SessionStart and SessionEnd hooks into Codex's user hooks file, preserving every existing entry.

func EnsureOpenCodeBindPlugin added in v0.10.0

func EnsureOpenCodeBindPlugin() (bool, error)

EnsureOpenCodeBindPlugin installs the Warren binding plugin for OpenCode. It is the hook-equivalent for OpenCode: on session.created/status=busy the plugin reads WARREN_SESSION_ID and atomically writes the binding file so the daemon can resolve the SQLite session without cwd+time guessing.

func InjectClaudeSessionID

func InjectClaudeSessionID(command, warrenSessionID string) string

InjectClaudeSessionID makes Claude's transcript path deterministic for a Warren session. Existing resume/session flags are left untouched so a user who explicitly resumes an older conversation keeps their intent.

func OpenCodeCachePath added in v0.10.0

func OpenCodeCachePath(warrenSessionID, opencodeSessionID string) string

OpenCodeCachePath is deterministic across daemon restarts and includes both Warren and OpenCode IDs, preventing a /clear or a reused Warren tab from consuming another conversation's projection.

func OpenCodeDataRoot added in v0.10.0

func OpenCodeDataRoot(override string) string

OpenCodeDataRoot returns OpenCode's application data directory. The explicit override is intended for tests and embedders. Warren's own override is deliberately namespaced so it cannot be mistaken for an OpenCode setting (the provider currently follows XDG_DATA_HOME and platform defaults).

func OpenCodeDatabasePath added in v0.10.0

func OpenCodeDatabasePath(dataRoot string) string

OpenCodeDatabasePath returns the current SQLite database path.

func OpenCodePluginPath added in v0.10.0

func OpenCodePluginPath() string

OpenCodePluginPath returns the Warren-managed OpenCode plugin path. The plugin is auto-discovered from ~/.config/opencode/plugin/ and project .opencode/plugin/; global ensures remote/empty projects still bind.

func ProjectEvents

func ProjectEvents(events []api.AgentEvent, options ReadOptions) ([]api.AgentEvent, error)

ProjectEvents applies the same filtering and content limits as ReadTranscript to events already retained by a live Host watcher. This is used by remote session readers, where the transcript file lives on the Host rather than on the CLI machine.

func ReadAgentStatus

func ReadAgentStatus(path string) (api.AgentStatus, error)

ReadAgentStatus returns the status recorded by the managed hook, or a zero status when the file is missing or malformed.

func ReadTranscript

func ReadTranscript(ctx context.Context, provider, path string, options ReadOptions) ([]api.AgentEvent, error)

ReadTranscript parses one Codex, Claude, or OpenCode JSONL transcript into the same normalized events used by Warren's live agent view. The file is consumed line by line, so the reader never loads the whole transcript into memory.

func ReadTranscriptChunk added in v0.9.0

func ReadTranscriptChunk(path string, offset int64, limit int) ([]byte, int64, bool, error)

ReadTranscriptChunk returns one exact byte range from a bound transcript. It deliberately does not parse or normalize the JSONL: callers use it for the explicit full-transcript escape hatch and stream the result onward in small chunks. The returned EOF applies to the file size observed for this read; a concurrently-running Agent may append another record afterwards.

func RemoveBinding

func RemoveBinding(warrenSessionID string)

RemoveBinding cleans up the per-session file when the session is deleted.

func RemoveOpenCodeCache added in v0.10.0

func RemoveOpenCodeCache(path string) error

RemoveOpenCodeCache removes a persisted cache when no live tailer exists, while leaving missing files as a successful cleanup.

func StatePath

func StatePath(warrenSessionID string) string

StatePath is the per-session file the managed hook uses to report lifecycle changes to the daemon.

func ValidateOpenCodeCommand added in v0.10.0

func ValidateOpenCodeCommand(command string) error

ValidateOpenCodeCommand keeps every session.create caller from selecting a provider conversation that Warren did not create. The CLI performs the same validation for agent create, but Desktop and Web launch presets call session.create directly and must receive the invariant at the Host boundary.

func WriteAgentStatus

func WriteAgentStatus(path string, status api.AgentStatus) error

WriteAgentStatus persists a status reported by a hook. The write is atomic so the daemon never reads a half-written status file.

func WriteBinding

func WriteBinding(path string, binding Binding) error

WriteBinding persists the binding reported by a hook. It is safe for the hook and the daemon to race: both write the same structured JSON and the daemon only starts a watcher for a path that exists on disk.

Types

type ActivityTracker

type ActivityTracker struct {
	// contains filtered or unexported fields
}

ActivityTracker folds normalized transcript events into an AgentStatus. It owns lifecycle and liveness state; provider-specific attention observations can be applied through MarkAttention without teaching the tracker provider event names.

func NewActivityTracker

func NewActivityTracker() *ActivityTracker

NewActivityTracker starts an agent in the ready state: open and idle.

func (*ActivityTracker) Activity

func (t *ActivityTracker) Activity() api.AgentActivity

Activity returns the lifecycle portion of Status.

func (*ActivityTracker) ClearAttention

func (t *ActivityTracker) ClearAttention()

ClearAttention removes a pending human-facing condition without changing the current lifecycle state.

func (*ActivityTracker) DrainTurns

func (t *ActivityTracker) DrainTurns() []api.AgentTurn

DrainTurns returns lifecycle transitions observed since the previous call.

func (*ActivityTracker) Exited

func (t *ActivityTracker) Exited()

Exited marks the agent process as gone.

func (*ActivityTracker) MarkAttention

func (t *ActivityTracker) MarkAttention(kind api.AgentAttentionKind, reason, requestID string, since time.Time)

MarkAttention applies a provider-neutral human-facing observation. The reducer owns the resulting activity so callers cannot accidentally create a yellow state without the corresponding blocked/warning lifecycle state.

func (*ActivityTracker) Observe

func (t *ActivityTracker) Observe(event api.AgentEvent)

Observe advances the tracker from one normalized transcript event.

func (*ActivityTracker) Status

func (t *ActivityTracker) Status() api.AgentStatus

Status returns the current complete presentation state.

func (*ActivityTracker) Tick

func (t *ActivityTracker) Tick(now time.Time)

Tick notices a tool call that has made no progress for the liveness grace period. It intentionally produces a stalled warning, never an input or approval request.

func (*ActivityTracker) Turn

func (t *ActivityTracker) Turn() uint64

Turn returns the current turn number. It remains stable after completion so events emitted at the boundary can still be associated with that turn.

func (*ActivityTracker) TurnAborted

func (t *ActivityTracker) TurnAborted()

TurnAborted marks a turn the user interrupted. An intentional interruption is a normal return to idle, not an outstanding request for attention.

func (*ActivityTracker) TurnComplete

func (t *ActivityTracker) TurnComplete()

TurnComplete marks a finished turn. The agent is idle and ready for the next instruction.

func (*ActivityTracker) TurnFailed

func (t *ActivityTracker) TurnFailed()

TurnFailed marks a turn that ended with an error.

func (*ActivityTracker) TurnStarted

func (t *ActivityTracker) TurnStarted()

TurnStarted marks the beginning of a new turn and clears attention from a request that the new user turn supersedes.

type Binding

type Binding struct {
	Provider       string    `json:"provider"`
	SessionID      string    `json:"sessionId"`
	TranscriptPath string    `json:"transcriptPath"`
	Cwd            string    `json:"cwd,omitempty"`
	UpdatedAt      time.Time `json:"updatedAt"`
}

Binding records which Codex/Claude conversation belongs to one Warren session. It is written by the Warren-managed SessionStart hook (Codex) or derived from the deterministic `--session-id` flag (Claude), and read by the daemon so transcript discovery is keyed by the CLI's own session ID instead of guessing by cwd and mtime.

func ReadBinding

func ReadBinding(path string) (*Binding, error)

ReadBinding returns nil when the file does not exist or is malformed; a malformed file is transient while a hook is mid-write, so callers retry.

type BindingCandidatesFinder added in v0.10.0

type BindingCandidatesFinder interface {
	FindBindings(ctx context.Context, warrenSessionID, kind, workspacePath string, after time.Time) ([]*OpenCodeBinding, error)
}

BindingCandidatesFinder is an optional extension used when more than one OpenCode session is created in the same workspace at nearly the same time. A caller can skip candidates already bound to another Warren session instead of repeatedly selecting the same earliest row.

type BindingFinder added in v0.10.0

type BindingFinder interface {
	FindBinding(ctx context.Context, warrenSessionID, kind, workspacePath string, after time.Time) (*OpenCodeBinding, error)
	FindBindingBySessionID(ctx context.Context, warrenSessionID, workspacePath, opencodeSessionID string) (*OpenCodeBinding, error)
}

BindingFinder is an additive interface. Finder remains source-compatible with the Codex/Claude file finder, while OpenCode callers can obtain the complete backend/session binding needed for a stable tailer.

type DefaultFinder

type DefaultFinder struct {
	// CodexRoot is the Codex sessions directory (default ~/.codex/sessions).
	CodexRoot string
	// ClaudeRoot is the Claude Code projects directory (default ~/.claude/projects).
	ClaudeRoot string
	// OpenCodeRoot overrides OpenCode's current data directory containing
	// opencode.db.
	OpenCodeRoot string
}

DefaultFinder implements Finder for the stock Codex and Claude Code layouts.

func (DefaultFinder) Find

func (f DefaultFinder) Find(ctx context.Context, kind, workspacePath string, after time.Time) (string, error)

func (DefaultFinder) FindBinding added in v0.10.0

func (f DefaultFinder) FindBinding(ctx context.Context, warrenSessionID, kind, workspacePath string, after time.Time) (*OpenCodeBinding, error)

DefaultFinder implements the stock Codex/Claude finder and OpenCode's current SQLite binding finder. The old Find method remains available for the other providers and returns the cache path for OpenCode.

func (DefaultFinder) FindBindingBySessionID added in v0.10.0

func (f DefaultFinder) FindBindingBySessionID(ctx context.Context, warrenSessionID, workspacePath, opencodeSessionID string) (*OpenCodeBinding, error)

func (DefaultFinder) FindBindings added in v0.10.0

func (f DefaultFinder) FindBindings(ctx context.Context, warrenSessionID, kind, workspacePath string, after time.Time) ([]*OpenCodeBinding, error)

type Finder

type Finder interface {
	Find(ctx context.Context, kind, workspacePath string, after time.Time) (string, error)
}

Finder locates the transcript file for a running Codex or Claude session. OpenCode uses BindingFinder to resolve its database-backed conversation and returns a Warren-owned cache path through this compatibility method. A missing file is not an error: the CLI may not be installed, may not have written a transcript yet, or may be running a version with a different layout. Callers retry until the file appears.

type OpenCodeBinding added in v0.10.0

type OpenCodeBinding struct {
	Provider      string `json:"provider"`
	SessionID     string `json:"sessionId"`
	Backend       string `json:"backend"`
	DataRoot      string `json:"dataRoot,omitempty"`
	DatabasePath  string `json:"databasePath,omitempty"`
	CachePath     string `json:"cachePath"`
	WorkspacePath string `json:"workspacePath,omitempty"`
}

OpenCodeBinding is the immutable identity used by one Warren session. A binding points at one OpenCode conversation, not at whichever conversation happens to be newest in the same checkout on the next poll.

func (OpenCodeBinding) Valid added in v0.10.0

func (b OpenCodeBinding) Valid() bool

type OpenCodeTailer added in v0.10.0

type OpenCodeTailer struct {
	// contains filtered or unexported fields
}

OpenCodeTailer polls one bound session and appends complete snapshots to its private cache. Failed writes and incomplete message/part races never mark a message as seen, so the next poll can recover the missing content.

func NewOpenCodeTailer added in v0.10.0

func NewOpenCodeTailer(binding OpenCodeBinding) (*OpenCodeTailer, error)

func StartOpenCodeSessionTailer added in v0.10.0

func StartOpenCodeSessionTailer(binding OpenCodeBinding) (*OpenCodeTailer, error)

StartOpenCodeSessionTailer starts the stable, bound tailer used by Service.

func StartOpenCodeTailer added in v0.10.0

func StartOpenCodeTailer(cachePath, workspace string, after time.Time) *OpenCodeTailer

StartOpenCodeTailer is retained as a small compatibility helper for callers that only have a workspace. New code should resolve and pass a binding.

func (*OpenCodeTailer) Close added in v0.10.0

func (t *OpenCodeTailer) Close()

func (*OpenCodeTailer) Path added in v0.10.0

func (t *OpenCodeTailer) Path() string

Path returns the Warren-owned JSONL cache consumed by the transcript watcher.

func (*OpenCodeTailer) Poll added in v0.10.0

func (t *OpenCodeTailer) Poll(parent context.Context) (err error)

Poll performs one bounded read and is exported for deterministic fixtures.

func (*OpenCodeTailer) RemoveCache added in v0.10.0

func (t *OpenCodeTailer) RemoveCache() error

RemoveCache is reserved for explicit Warren session deletion. Close keeps the cache so a daemon restart can restore the structured conversation.

type ReadOptions

type ReadOptions struct {
	Recent       int
	ContentLimit int
	Full         bool
	IncludeTypes []string
	ExcludeTypes []string
	// Tools adds compact tool-call records to the conversation projection.
	// ToolOutput additionally includes the bounded raw tool results.
	ToolOutput bool
	Tools      bool
}

ReadOptions controls how a transcript is projected for an external caller. A zero Recent means that every matching event is returned. ContentLimit is ignored when Full is true; a zero ContentLimit uses the default limit.

type Watcher

type Watcher struct {
	// contains filtered or unexported fields
}

Watcher tails one transcript file and emits normalized events. The first poll replays existing history; later polls only deliver newly appended lines. A truncated file restarts from byte zero without re-emitting the already-served prefix, which is acceptable for a best-effort side channel.

func Start

func Start(
	sessionID, provider, path string,
	onEvents func([]api.AgentEvent, api.AgentStatus),
	onStatus func(api.AgentStatus),
	onTurns func([]api.AgentTurn, bool),
) *Watcher

Start begins tailing path immediately in a background goroutine.

func (*Watcher) Close

func (w *Watcher) Close()

Close stops the poll loop and waits for it to finish.

func (*Watcher) Path

func (w *Watcher) Path() string

Path returns the transcript file being watched.

func (*Watcher) Snapshot

func (w *Watcher) Snapshot() []api.AgentEvent

Snapshot returns the retained event history.

func (*Watcher) WaitReady

func (w *Watcher) WaitReady(ctx context.Context) error

WaitReady waits until the initial transcript replay has established the current status and turn cursor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL