Skip to content

agent

View on pkg.go.dev

import "github.com/thesimonho/warden/agent"

Package agent defines interfaces and types for extracting status data from CLI agents running inside project containers. The abstraction allows supporting different agent backends (Claude Code, Aider, etc.) without coupling the dashboard to any single agent’s data format.

Type aliases for convenience — re-export from constants so existing callers (agent.ClaudeCode, agent.DefaultType, etc.) still work.

const (
ClaudeCode = constants.AgentClaudeCode
Codex = constants.AgentCodex
DefaultType = constants.DefaultAgentType
)

const (
// ClaudeCodeVersion is the pinned Claude Code CLI version.
// Query latest: curl -sfL "https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest"
ClaudeCodeVersion = "2.1.92"
// CodexVersion is the pinned OpenAI Codex CLI version.
// Query latest: npm view @openai/codex version
CodexVersion = "0.118.0"
)

MaxPromptLength is the maximum length of user prompt text included in events. Matches the truncation in container event scripts for consistency.

const MaxPromptLength = 500

MaxToolInputLength is the maximum length of tool input included in events.

const MaxToolInputLength = 1000

AllTypes lists all supported agent type identifiers in display order.

var AllTypes = constants.AllAgentTypes

DisplayLabels maps agent type identifiers to human-readable labels.

var DisplayLabels = map[constants.AgentType]string{
ClaudeCode: "Claude Code",
Codex: "OpenAI Codex",
}

func ShortLabel(agentType constants.AgentType) string

ShortLabel returns a compact display label for the given agent type, falling back to the type string itself when no mapping exists.

func TruncateString(s string, maxLen int) string

TruncateString caps a string at maxLen runes, appending ”…” if truncated. Uses rune count to avoid splitting multi-byte UTF-8 characters.

func VersionForType(agentType constants.AgentType) string

VersionForType returns the pinned CLI version for the given agent type.

func WorktreeIDFromCWD(cwd string) string

WorktreeIDFromCWD extracts the worktree ID from a container-side working directory. Returns “main” for the workspace root or unrecognized paths.

Patterns:

  • .claude/worktrees/<id> → <id> (Claude Code native worktrees)
  • .warden/worktrees/<id> → <id> (Warden-managed worktrees for Codex)

FormatPromptResult holds the cleaned prompt text and its classified source.

type FormatPromptResult struct {
Text string
Source PromptSource
}

func FormatPromptText(text string) FormatPromptResult

FormatPromptText cleans up raw prompt text from agent session files and classifies the prompt source. Claude Code’s ! bash mode and /slash commands wrap content in XML-like tags that are not useful for audit display.

Returns empty Text for prompts that contain only stripped tags (e.g. local-command-caveat instructions). Source is classified as:

  • “bash” for <bash-input> commands
  • “bash_output” for <bash-stdout>/<bash-stderr> output
  • “user” for plain text prompts

ModelInfo identifies the model being used by the agent.

type ModelInfo struct {
ID string
DisplayName string
}

ParsedEvent is an agent-agnostic event produced by parsing a session JSONL line. The parser converts agent-specific JSONL formats into these uniform events, which are then converted to [event.ContainerEvent] for SSE broadcast and audit logging.

type ParsedEvent struct {
// Type identifies what kind of event this is.
Type ParsedEventType
// SessionID is the agent's session identifier.
SessionID string
// Timestamp is when the event occurred (ISO 8601).
Timestamp string
// Model is the AI model used (populated on assistant events).
Model string
// ToolName is the tool invoked (populated on ToolUse events).
ToolName string
// ToolInput is a summary of the tool input (populated on ToolUse events, truncated).
ToolInput string
// Prompt is the user's message text (populated on UserPrompt events).
Prompt string
// PromptSource classifies the origin of the prompt (populated on UserPrompt events).
// Values: "user" (normal text), "bash" (! command), "bash_output" (! stdout/stderr).
PromptSource PromptSource
// ErrorContent is the error message (populated on ToolUseFailure and StopFailure events).
ErrorContent string
// ServerName is the MCP server name (populated on Elicitation events).
ServerName string
// DurationMs is the turn duration in milliseconds (populated on TurnDuration events).
DurationMs int64
// Tokens holds cumulative token usage (populated on TokenUpdate events).
Tokens TokenUsage
// EstimatedCostUSD is the estimated cost from tokens (populated on TokenUpdate events).
EstimatedCostUSD float64
// GitBranch is the current git branch (populated on SessionStart).
GitBranch string
// WorktreeID is the worktree identifier (populated on SessionStart if available).
WorktreeID string
// Subtype is the system message subtype (populated on SystemInfo events).
Subtype string
// Content is the message text (populated on SystemInfo, SubagentStop, PermissionGrant, ContextCompact events).
Content string
// Commands holds allowed commands (populated on PermissionGrant events).
Commands []string
// TTFTMs is time to first token in milliseconds (populated on ApiMetrics events).
TTFTMs float64
// OutputTokensPerSec is output tokens per second (populated on ApiMetrics events).
OutputTokensPerSec float64
// CompactTrigger is what triggered context compaction (populated on ContextCompact events).
CompactTrigger string
// PreCompactTokens is the token count before compaction (populated on ContextCompact events).
PreCompactTokens int64
// SourceLine is the raw JSONL line bytes that produced this event.
// Used to compute a content hash for deduplication in the audit DB.
// Set by the session watcher; empty for hook-sourced events.
SourceLine []byte
// SourceIndex disambiguates multiple events parsed from the same JSONL line.
SourceIndex int
}

func ParseAllEvents(parser SessionParser, r io.Reader) ([]ParsedEvent, error)

ParseAllEvents reads a JSONL stream line-by-line and returns all parsed events. Used by test helpers to collect events for detailed assertions.

ParsedEventType is an alias for [event.ContainerEventType]. Parsers use the same constants as the event pipeline directly. The only exception is [event.EventTokenUpdate], which the session bridge remaps to [event.EventCostUpdate] before entering the store.

type ParsedEventType = event.ContainerEventType

ProjectInfo provides project metadata for session file discovery.

type ProjectInfo struct {
// ProjectID is the deterministic 12-char hex project identifier.
ProjectID string
// AgentType identifies the agent (e.g. "claude-code", "codex").
AgentType string
// WorkspaceDir is the container-side workspace directory.
WorkspaceDir string
// ProjectName is the user-chosen project name.
ProjectName string
}

PromptSource identifies the origin of a user prompt for display purposes.

type PromptSource string

const (
// PromptSourceUser is a normal text prompt typed by the user.
PromptSourceUser PromptSource = "user"
// PromptSourceBash is a command run via Claude Code's ! bash mode.
PromptSourceBash PromptSource = "bash"
// PromptSourceBashOutput is stdout/stderr output from a ! bash command.
PromptSourceBashOutput PromptSource = "bash_output"
)

func (s PromptSource) IsBash() bool

IsBash returns true if the prompt originated from a ! bash command (either the command itself or its stdout/stderr output).

Registry holds StatusProvider instances keyed by agent type. It allows the engine to resolve the correct provider for a container based on its WARDEN_AGENT_TYPE env var.

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

func NewRegistry() *Registry

NewRegistry creates an empty agent registry.

func (r *Registry) Default() StatusProvider

Default returns the StatusProvider for the default agent type (claude-code). Returns nil if the default provider is not registered.

func (r *Registry) Get(agentType constants.AgentType) (StatusProvider, bool)

Get returns the StatusProvider for the given agent type. Returns nil and false if no provider is registered for that type.

func (r *Registry) Register(agentType constants.AgentType, provider StatusProvider)

Register adds a StatusProvider for the given agent type.

func (r *Registry) Resolve(agentType constants.AgentType) StatusProvider

Resolve returns the StatusProvider for the given agent type, falling back to the default provider if the type is empty or unregistered.

SessionParser parses agent-specific JSONL session files into agent-agnostic ParsedEvents. Each agent implementation (Claude Code, Codex) provides its own parser that knows the JSONL schema.

Parsers are stateful — they accumulate token counts across lines within a session. Create a new parser per session file.

type SessionParser interface {
// ParseLine parses a single JSONL line into zero or more Warden events.
// Returns nil for lines that don't produce events (e.g. file-history-snapshot).
ParseLine(line []byte) []ParsedEvent
// SessionDir returns the host-side directory to watch for session files.
// The directory path is constructed from the host home dir and project metadata.
SessionDir(homeDir string, project ProjectInfo) string
// FindSessionFiles returns the absolute paths of active JSONL session
// files belonging to the given project. Returns nil when no files are
// found or the directory does not exist.
FindSessionFiles(homeDir string, project ProjectInfo) []string
}

SessionWatcher monitors a directory for JSONL session files and tails them line-by-line, feeding parsed events to a callback. It handles session file rotation (new session → new .jsonl file appears).

Lifecycle: one watcher per project, created when a container starts, stopped when the container stops.

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

func NewSessionWatcher(parser SessionParser, homeDir string, project ProjectInfo, callback func(ParsedEvent), offsetStore watcher.OffsetStore) *SessionWatcher

NewSessionWatcher creates a watcher for JSONL session files. The parser converts lines into ParsedEvents. The callback receives each event (typically wired to the eventbus). The homeDir and project are passed to the parser’s FindSessionFiles for file discovery. The offsetStore (optional, may be nil) persists byte offsets so the tailer resumes from where it left off after a restart.

func (sw *SessionWatcher) Start(ctx context.Context) error

Start begins watching for session files. It discovers existing session files via the parser’s FindSessionFiles and tails them. Periodically re-discovers to pick up new sessions.

func (sw *SessionWatcher) Stop()

Stop signals the watcher to stop and waits for goroutines to finish.

Status holds agent-reported data for a single session/project directory. Fields are optional — not all agents report all fields.

type Status struct {
// CostUSD is the total session cost in US dollars.
CostUSD float64
// DurationMs is the total wall-clock time since the session started.
DurationMs int64
// APIDurationMs is the time spent waiting for API responses.
APIDurationMs int64
// LinesAdded is the total number of lines added during the session.
LinesAdded int
// LinesRemoved is the total number of lines removed during the session.
LinesRemoved int
// Model holds the agent model information.
Model ModelInfo
// Tokens holds token usage counters.
Tokens TokenUsage
// AgentSessionID is the agent's own session identifier (not the dashboard's).
AgentSessionID string
}

StatusProvider extracts agent status data from a config file that the agent writes inside the container. Each agent implementation knows where its data lives and how to parse it.

The Docker layer reads the file via exec and passes raw bytes here. The provider returns status data keyed by working directory path, which the caller uses to match against dashboard sessions.

type StatusProvider interface {
// Name returns a human-readable agent identifier (e.g. "claude-code").
Name() string
// ProcessName returns the CLI binary name used for pgrep process detection
// (e.g. "claude", "codex"). This is the executable name, not the agent type.
ProcessName() string
// ConfigFilePath returns the absolute path to the agent's config file
// inside the container (e.g. "/home/warden/.claude.json").
ConfigFilePath() string
// ExtractStatus parses the config file contents and returns status data
// keyed by the working directory path that the agent was running in.
//
// For agents that use worktrees, the key is the worktree path
// (e.g. "/project/.claude/worktrees/abc-123"). For non-worktree sessions,
// the key is the project root (e.g. "/project").
//
// Returns nil for keys where no status data is available.
// Returns an empty map if the config data is empty or unparseable.
ExtractStatus(configData []byte) map[string]*Status
// NewSessionParser creates a new stateful parser for JSONL session files.
// Each parser instance accumulates state (e.g. token counts) across lines,
// so a new parser should be created per session file. Returns nil if the
// provider does not support JSONL parsing.
NewSessionParser() SessionParser
}

TokenUsage holds token consumption counters.

type TokenUsage struct {
InputTokens int64
OutputTokens int64
CacheReadTokens int64
CacheWriteTokens int64
}

ValidationResult holds the outcome of parsing a JSONL session file.

type ValidationResult struct {
// TotalEvents is the total number of parsed events.
TotalEvents int
// Counts maps each event type to how many times it appeared.
Counts map[ParsedEventType]int
// Errors collects any requirement failures from [Require].
Errors []string
}

func ValidateJSONL(parser SessionParser, r io.Reader) (*ValidationResult, error)

ValidateJSONL reads a JSONL session file line-by-line, parses each line with the given parser, and returns a ValidationResult with event counts. This is the shared validation logic used by both unit tests (against test fixtures) and CI (against live CLI output).

func (v *ValidationResult) Check() error

Check returns an error if any [Require] calls failed, or if no events were parsed at all. Returns nil on success.

func (v *ValidationResult) Require(eventType ParsedEventType, minCount int)

Require asserts that at least minCount events of the given type were parsed. Failures are accumulated in Errors and surfaced by [Check].

Generated by gomarkdoc