Documentation
¶
Overview ¶
Package pi implements the Agent interface for the pi coding agent (https://github.com/earendil-works/pi-mono). The npm package the embedded extension imports a type from is `@earendil-works/pi-coding-agent`.
This is an in-tree port of the previously-external entire-agent-pi plugin (github.com/entireio/external-agents/agents/entire-agent-pi). The behaviour matches the external version — most notably the active-branch resolution for Pi's tree-shaped sessions — but the integration is plumbed directly through the in-tree Agent / HookSupport / TokenCalculator / TranscriptAnalyzer interfaces rather than the external JSON-over-stdio protocol.
Index ¶
- Constants
- func NewPiAgent() agent.Agent
- type PiAgent
- func (a *PiAgent) AreHooksInstalled(ctx context.Context) bool
- func (a *PiAgent) CalculateTokenUsage(transcriptData []byte, fromOffset int) (*agent.TokenUsage, error)
- func (a *PiAgent) ChunkTranscript(_ context.Context, content []byte, maxSize int) ([][]byte, error)
- func (a *PiAgent) Description() string
- func (a *PiAgent) DetectPresence(ctx context.Context) (bool, error)
- func (a *PiAgent) ExtractModifiedFilesFromOffset(path string, startOffset int) ([]string, int, error)
- func (a *PiAgent) ExtractPrompts(sessionRef string, fromOffset int) ([]string, error)
- func (a *PiAgent) FormatResumeCommand(sessionID string) string
- func (a *PiAgent) GetSessionBaseDir() (string, error)
- func (a *PiAgent) GetSessionDir(repoPath string) (string, error)
- func (a *PiAgent) GetSessionID(input *agent.HookInput) string
- func (a *PiAgent) GetSupportedHooks() []agent.HookType
- func (a *PiAgent) GetTranscriptPosition(path string) (int, error)
- func (a *PiAgent) HookNames() []string
- func (a *PiAgent) InstallHooks(ctx context.Context, localDev bool, force bool) (int, error)
- func (a *PiAgent) IsPreview() bool
- func (a *PiAgent) Name() types.AgentName
- func (a *PiAgent) ParseHookEvent(ctx context.Context, hookName string, stdin io.Reader) (*agent.Event, error)
- func (a *PiAgent) ProtectedDirs() []string
- func (a *PiAgent) ProtectedFiles() []string
- func (a *PiAgent) ReadSession(input *agent.HookInput) (*agent.AgentSession, error)
- func (a *PiAgent) ReadTranscript(sessionRef string) ([]byte, error)
- func (a *PiAgent) ReassembleTranscript(chunks [][]byte) ([]byte, error)
- func (a *PiAgent) ResolveSessionFile(sessionDir, agentSessionID string) string
- func (a *PiAgent) Type() types.AgentType
- func (a *PiAgent) UninstallHooks(ctx context.Context) error
- func (a *PiAgent) WriteSession(_ context.Context, session *agent.AgentSession) error
Constants ¶
const ( HookNameSessionStart = "session_start" HookNameBeforeAgentStart = "before_agent_start" HookNameAgentEnd = "agent_end" HookNameSessionShutdown = "session_shutdown" )
Hook names — these match Pi's native event names exactly (snake_case), because the embedded TypeScript extension forwards `pi.on(<event>)` events directly. Keeping the names identical avoids a translation layer in the extension.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type PiAgent ¶
type PiAgent struct{}
PiAgent implements agent.Agent for the pi coding agent.
func (*PiAgent) AreHooksInstalled ¶
AreHooksInstalled returns true when the extension file exists and is recognisable as Entire-owned (contains the marker string).
func (*PiAgent) CalculateTokenUsage ¶
func (a *PiAgent) CalculateTokenUsage(transcriptData []byte, fromOffset int) (*agent.TokenUsage, error)
CalculateTokenUsage sums per-assistant-message token usage from a Pi JSONL transcript starting at the given line offset. Only assistant messages on the active conversation branch contribute to the totals — see pijsonl.ResolveActiveBranch for the rationale.
func (*PiAgent) ChunkTranscript ¶
ChunkTranscript splits a Pi JSONL transcript at line boundaries.
func (*PiAgent) Description ¶
func (*PiAgent) DetectPresence ¶
DetectPresence reports whether pi is configured for *this repo*. We only check repo-local config (.pi/) and intentionally ignore $PATH — in-tree agents follow the convention used by Claude/Gemini/OpenCode where detection means "this repo is set up for this agent", not "this agent is installed somewhere on this machine". The external plugin uses the broader $PATH check because it can't see repo state; we don't have that limitation.
func (*PiAgent) ExtractModifiedFilesFromOffset ¶
func (a *PiAgent) ExtractModifiedFilesFromOffset(path string, startOffset int) ([]string, int, error)
ExtractModifiedFilesFromOffset scans Pi assistant tool calls from startOffset onward and returns file paths touched by file-modifying tools (`write`, `edit`). Branch-aware: only counts entries on the active conversation branch.
func (*PiAgent) ExtractPrompts ¶
ExtractPrompts returns user-message text from the transcript starting at the given line offset. Branch-aware (drops abandoned-branch prompts).
func (*PiAgent) FormatResumeCommand ¶
FormatResumeCommand returns the shell command to resume a specific Pi session by ID. Pi accepts a partial UUID via `pi --session <id>`. When no session is specified, fall back to `pi --continue` which reopens the most recent session.
func (*PiAgent) GetSessionBaseDir ¶
GetSessionBaseDir returns the base directory containing per-project session subdirectories. Used by attach's cross-project fallback (searchTranscriptInProjectDirs) when a session was started from a different cwd than the current worktree root.
func (*PiAgent) GetSessionDir ¶
GetSessionDir returns the directory where Pi natively stores session transcripts for repoPath: <piHome>/sessions/<encoded-repo-path>/.
Pointing this at the native store (rather than the per-repo .entire/tmp/pi/ cache populated by the agent_end hook) is what lets `entire session attach <id>` resolve cold sessions — sessions that were never hooked, or whose hook capture failed. attach falls through to GetSessionDir + ResolveSessionFile when no SessionRef is recorded in metadata, and the live Pi store is the only place that always has the transcript on disk.
Resolution order:
- ENTIRE_TEST_PI_SESSION_DIR (test override; no encoding applied)
- PI_CODING_AGENT_DIR (Pi's own override; encoding still applies)
- ~/.pi/agent (default)
The .entire/tmp/pi/ cache stays as a hook-internal detail — captureTranscript writes there and the TurnEnd event records that path as SessionRef in checkpoint metadata, so subsequent operations on hooked sessions go through the recorded SessionRef and never call GetSessionDir.
func (*PiAgent) GetSessionID ¶
GetSessionID extracts the session ID from a hook input.
func (*PiAgent) GetSupportedHooks ¶
GetSupportedHooks maps Pi's native events to normalised lifecycle types.
- session_start → SessionStart
- before_agent_start → TurnStart
- agent_end → TurnEnd
- session_shutdown → (cleanup-only, no lifecycle event — see ParseHookEvent)
func (*PiAgent) GetTranscriptPosition ¶
GetTranscriptPosition returns the JSONL line count of the file at path. Used by the strategy as the offset for incremental ExtractModifiedFiles calls. Missing files report 0 (consistent with Claude Code).
func (*PiAgent) InstallHooks ¶
InstallHooks writes the Entire pi extension to .pi/extensions/entire/index.ts. Returns 1 if the extension was written, 0 if already up-to-date (idempotent). If the file exists but content differs (e.g., localDev vs production), it is rewritten as long as it is recognisable as Entire-owned (contains the marker). A foreign file at the same path is left untouched unless force is true — this protects user-authored extensions that happen to live at .pi/extensions/entire/index.ts.
func (*PiAgent) ParseHookEvent ¶
func (a *PiAgent) ParseHookEvent(ctx context.Context, hookName string, stdin io.Reader) (*agent.Event, error)
ParseHookEvent translates a Pi hook invocation into a normalised lifecycle event. Implements agent.HookSupport.
func (*PiAgent) ProtectedDirs ¶
func (*PiAgent) ProtectedFiles ¶
func (*PiAgent) ReadSession ¶
ReadSession loads a captured Pi transcript and returns it as an AgentSession.
func (*PiAgent) ReadTranscript ¶
ReadTranscript reads a captured Pi JSONL session transcript from disk. SessionRef is the absolute path returned by captureTranscript().
func (*PiAgent) ReassembleTranscript ¶
ReassembleTranscript concatenates JSONL chunks with newlines.
func (*PiAgent) ResolveSessionFile ¶
ResolveSessionFile returns the path to the Pi session file for agentSessionID in sessionDir. Pi names files <timestamp>_<id>.jsonl, so glob for the matching ID; on multiple matches the lexicographically latest (most recent timestamp) wins.
Absolute paths pass through unchanged so hook payloads carrying live pi paths work without re-resolution. When no match exists, fall back to a deterministic non-existent path so downstream stat checks fail cleanly rather than panicking on an empty path.
func (*PiAgent) UninstallHooks ¶
UninstallHooks removes the entire pi extension directory (if present).
func (*PiAgent) WriteSession ¶
WriteSession writes a captured Pi transcript back to disk so Pi can resume from it. Pi loads sessions from arbitrary paths via `pi --session <path>`, so a plain write is sufficient.