Documentation
¶
Overview ¶
Package livesessions tracks the coding-agent sessions running on this machine from the hook events the daemon records, and serves them to the local application so a pull request page and the inbox can show work in progress before any session file is complete.
It reads identifiers only, and one line of text. A hook event never carries prompts or tool input, and the tracker keeps from it the agent, the session id, when the session started and was last active, a normalised event kind, why and since when it waits on its user, with the short message a Claude Code notification showed (masked like a transcript), and the repository, branch and pull request the session's working directory links to, else the one its agent's latest commit or push reaches (work.go). The working directory itself, the transcript path and the terminal the hooks say the session runs in (processes, terminal device and terminal variables, which Focus uses to bring it forward) stay inside the tracker and are never served. Reading session files from disk is unchanged and remains the full history; Transcripts serves a tracked session's transcript, redacted, while it runs, and its list adds each session's title, compaction summary and activity (latest tool calls, files written, latest assistant message), read from the transcript and masked.
Index ¶
- Constants
- Variables
- type Activity
- type Answer
- type ClaudeAlive
- type ClaudeExit
- type ClaudeInbox
- type ClaudeProcess
- type CodexQueue
- type Discovery
- type Ender
- type Focuser
- type Link
- type Linker
- type Listed
- type Location
- type RecordedLinker
- type Registry
- type Resolver
- func (r *Resolver) Link(ctx context.Context, cwd string) (Link, bool)
- func (r *Resolver) LinkCommit(ctx context.Context, dir, branch, head string) (Link, bool)
- func (r *Resolver) LinkPush(ctx context.Context, dir, remote, branch, head string) (Link, bool)
- func (r *Resolver) Relink(ctx context.Context, s Session) (Link, bool)
- type Sender
- type Session
- type Source
- type Step
- type Tracker
- func (t *Tracker) Answered(agent, id string, at time.Time) bool
- func (t *Tracker) Apply(_ context.Context, e hookevents.Event) bool
- func (t *Tracker) Gone(agent, id string, at time.Time) bool
- func (t *Tracker) Handler() http.Handler
- func (t *Tracker) Interrupted(agent, id string, at time.Time) bool
- func (t *Tracker) Lookup(agent, id string) (Session, string, bool)
- func (t *Tracker) Rebuild(ctx context.Context)
- func (t *Tracker) Reconcile(ctx context.Context) bool
- func (t *Tracker) ResolvePending(ctx context.Context) bool
- func (t *Tracker) Resumed(agent, id string, at time.Time) bool
- func (t *Tracker) Run(ctx context.Context)
- func (t *Tracker) Sessions() []Session
- func (t *Tracker) Sweep() bool
- func (t *Tracker) Terminal(agent, id string) (*hookevents.Terminal, bool)
- type Transcripts
- func (t *Transcripts) Get(ctx context.Context, agent, id string) (Answer, error)
- func (t *Transcripts) Handler() http.Handler
- func (t *Transcripts) Image(ctx context.Context, agent, id, turn string, n int) (string, []byte, error)
- func (t *Transcripts) Read(ctx context.Context, agent, id, path, branch string) (Answer, error)
- func (t *Transcripts) Watch(ctx context.Context, notify func())
- func (t *Transcripts) WriteAnswer(w http.ResponseWriter, r *http.Request, answer Answer, err error)
- type Unavailable
- type WorkHistory
- type WorkLinker
- type WorkObservation
Constants ¶
const ( TitleRecheckEvery = 5 * time.Second WorkingRecheckEvery = time.Second CursorTitleRecheckEvery = 30 * time.Second )
Bounds on what is read of a session to describe it. A session is looked at no more than once per TitleRecheckEvery, or WorkingRecheckEvery while it works, so the app follows what it says within a couple of seconds (CursorTitleRecheckEvery for Cursor, whose database is one large file it writes all the time), and its files are read again only when they changed. Claude Code and Codex append to their transcripts, so each read starts where the last one stopped, at most scanPass bytes at a time; until a long transcript is caught up, its last titleTail bytes, where Claude Code rewrites the title as the session goes, name it.
const ( // StateRunning is a session whose agent is working: its last event was // a start, a prompt or tool activity, and not longer ago than IdleAfter. StateRunning = "running" // StateIdle is a session waiting for its user (it has a WaitingReason: // its turn ended, or it asked for a permission or an answer) or silent // for IdleAfter: an agent that crashed sends nothing. StateIdle = "idle" // StateEnded is a session that sent its end event. StateEnded = "ended" )
States of a tracked session.
const ( EventStart = "start" EventPrompt = "prompt" EventTool = "tool" // EventWait is the agent asking its user for something: a Claude Code // notification or a permission request of either agent. EventWait = "wait" EventStop = "stop" EventEnd = "end" )
Normalised kinds of the last event, the same for every agent.
const ( // WaitPermission is a session asking to be allowed to run a tool: a // Claude Code permission_prompt notification or a permission request of // either agent. WaitPermission = "permission" // WaitInput is a session asking a question (an elicitation dialog, a // background agent waiting on input) or any notification of a type baum // does not know, which is taken to want the user. WaitInput = "input" // WaitTurnFinished is a session whose turn ended (a stop, or Claude // Code's idle_prompt, which follows one a minute later). WaitTurnFinished = "turn_finished" // WaitQuiet is a session silent for IdleAfter with no tool call of its // own still running: it stopped without a stop (an interrupted turn // sends none) or its agent died. The list sets it (settle), since only // the transcript says whether a tool call still runs. WaitQuiet = "quiet" )
Why a session waits on its user.
const ( IdleAfter = 10 * time.Minute DropAfter = 2 * time.Hour EndedFor = 24 * time.Hour MaxSessions = 200 // SweepEvery is how often Run applies the timings, which events do not // trigger. SweepEvery = 30 * time.Second // RelinkEvery is the least time between two lookups of a session's // repository, branch and pull request: the branch changes while a // session runs, and a lookup runs git. RelinkEvery = 30 * time.Second // NotifyEvery coalesces the notifications of a busy session. NotifyEvery = time.Second // ActivityNotifyEvery is how far last_activity_at must move, with nothing // else changed, for the app to be told: a busy agent's tool events would // otherwise make every open window read the list once a second. ActivityNotifyEvery = 30 * time.Second // AnnounceWithin is how recent a wait must be for Waiting to be told of // it: an event the spool delivers late is old news. AnnounceWithin = 5 * time.Minute // StateFile is the name of the file in StateDir holding the ended // sessions, since the hook event record rotates by size and may not reach // back EndedFor, and the open ones, which a session waiting for its user // outlives the record's DropAfter in. StateFile = "live-sessions.json" )
Timings and bounds. A session is dropped when it has been silent for DropAfter, unless its agent is seen still running (Tracker.Alive), or ended for EndedFor, which is long enough for the Sessions page to show what ended recently; at most MaxSessions are tracked, the least recently active (an ended one first) making room for a new one.
const ( RecheckEvery = time.Second CursorRecheckEvery = 5 * time.Second MaxCached = 8 )
Transcript bounds. A session's file is looked at (a stat of each of its files) at most once per RecheckEvery, however many windows ask, and read again only when one of them changed. Cursor keeps every conversation in one database that it writes all the time, and finding one conversation in it reads the whole index, so it is looked at less often. At most MaxCached transcripts are held, the least recently asked for making room.
const ( AgentClaudeCode = "claude-code" AgentCodex = "codex" AgentCursor = "cursor" )
Agents the hooks name, as the tracker keys them.
const ( MethodPush = "push" MethodCommit = "commit" )
Link methods of a pull request found through a session's own Git work rather than its working directory.
Variables ¶
var ErrAgentGone = errors.New("the session's agent is not running")
ErrAgentGone is a session whose agent no longer runs: it ended without its end event reaching the daemon.
var ErrNoImage = errors.New("livesessions: no such image")
ErrNoImage is the answer for an image a session's transcript does not hold.
var ErrNotTracked = errors.New("no such live session")
ErrNotTracked is a session the tracker does not list: not one of this machine's agent sessions, or one that ended more than EndedFor ago.
Functions ¶
This section is empty.
Types ¶
type Activity ¶
type Activity struct {
// Steps are the latest tool calls, oldest first, at most maxSteps.
Steps []Step `json:"steps,omitempty"`
// FilesTouched is the number of distinct files the session wrote, over
// the whole transcript read so far; absent when none.
FilesTouched int `json:"files_touched,omitempty"`
// LastMessage is the latest assistant text, masked, its line breaks kept
// (block) and cut.
LastMessage string `json:"last_message,omitempty"`
// LastPrompt is the latest message the user typed, masked, on one line
// and cut.
LastPrompt string `json:"last_prompt,omitempty"`
// contains filtered or unexported fields
}
Activity is what a session's transcript says it is doing, as the list serves it.
type Answer ¶
type Answer struct {
Session Session
// Version changes whenever the transcript's files do.
Version string
// Body is the rendered transcript.
Body []byte
// Images are the media types of the images pasted with each prompt, by
// its turn, in order; nil when none were.
Images map[string][]string
// contains filtered or unexported fields
}
Answer is a session's transcript as served.
type ClaudeAlive ¶
type ClaudeAlive struct {
Home string
}
ClaudeAlive is a Tracker's Alive: whether the process of a Claude Code session still runs under home's ~/.claude (empty is the user's home), as FindClaude confirms it. Other agents register no process, so theirs are never confirmed.
func (ClaudeAlive) Alive ¶
func (c ClaudeAlive) Alive(agent, id string) bool
Alive is a Tracker's Alive.
type ClaudeExit ¶
type ClaudeExit struct {
// Home holds ~/.claude; empty is the user's home.
Home string
}
ClaudeExit ends Claude Code sessions as /exit does: SIGTERM to the session's process, which then exits in under a second, leaves its terminal at the shell, fires its SessionEnd hook (reason "other") and can be resumed with `claude --resume <id>`. Only a process that reports itself idle is signalled: a busy one would lose the turn it is in.
type ClaudeInbox ¶
type ClaudeInbox struct {
// Home holds ~/.claude; empty is the user's home.
Home string
}
ClaudeInbox sends to Claude Code sessions through their cross-session inbox (https://code.claude.com/docs/en/cross-session-messaging): one JSON line on the session's socket, which an idle session takes as a new turn, in its own terminal and transcript. A session's crossSessionInbound setting can hold or refuse it. On macOS and Linux only: on Windows the inbox is a named pipe.
type ClaudeProcess ¶
type ClaudeProcess struct {
PID int `json:"pid"`
SessionID string `json:"sessionId"`
// Status is "idle" or "busy".
Status string `json:"status"`
// Socket is the session's cross-session inbox, a Unix socket.
Socket string `json:"messagingSocketPath"`
Kind string `json:"kind"`
// ProcStart is when the process started as its system counts it (on
// Linux, field 22 of /proc/<pid>/stat), and PIDDomain the pid namespace
// the pid is in ("linux::pid:[…]"): Claude Code records both, so a pid a
// later process reused, under an entry a killed session left, is told
// apart from the session's own.
ProcStart string `json:"procStart"`
PIDDomain string `json:"pidDomain"`
}
ClaudeProcess is a running Claude Code session as Claude Code registers it in ~/.claude/sessions/<pid>.json.
func FindClaude ¶
func FindClaude(home, id string) (ClaudeProcess, error)
FindClaude is the running process of Claude Code session id under home's ~/.claude/sessions, or ErrAgentGone: an entry naming the session whose process still runs, and is the one that wrote it (sameProcess). Claude Code removes an entry when its session exits; one a killed process left is passed over. A process baum cannot confirm is an error, not ErrAgentGone, so it is neither signalled nor recorded ended.
type CodexQueue ¶
type CodexQueue struct {
// Codex is the codex command, looked up on PATH when the daemon starts.
Codex string
}
CodexQueue sends to Codex sessions with Codex's own `codex queue --thread <id> --message <text>` (0.149 and later): the message arrives in the same thread as a turn the user typed, after the current one when the session is busy. A thread whose terminal was closed still runs it, in Codex's shared daemon, which is why the app offers it only after a finished turn.
func NewCodexQueue ¶
func NewCodexQueue() (CodexQueue, bool)
NewCodexQueue is a CodexQueue for the codex command on PATH, or false when there is none.
type Discovery ¶
Discovery reads cached open pull requests and terminal observations without a request to GitHub; *discovery.Service is one.
type Ender ¶
Ender stops the agent of one of its sessions, by the session's agent and id, as if its user quit it: the agent's own end hook then says it ended. It returns ErrAgentGone when no process of the session is running.
type Focuser ¶
type Focuser interface {
Predict(agent string, terminal *hookevents.Terminal) string
Focus(ctx context.Context, agent, id string, terminal *hookevents.Terminal) focus.Result
}
Focuser brings a session forward from where its hooks said it runs; *focus.Focuser is one.
type Link ¶
type Link struct {
// Repository is the registered clone's repository, "owner/name" in
// lower case.
Repository string
// Branch is the branch checked out, empty when detached.
Branch string
// PR is the pull request of Branch, 0 when none is known.
PR int
PRState string
HeadSHA, Upstream, Method, Status string
HeadRepository, UpstreamRepository string
}
Link is where a working directory belongs.
type Linker ¶
Linker links a hook's working directory to a repository, branch and pull request, and reports false for a directory in no registered clone.
type Listed ¶
type Listed struct {
Session
// Title is the session's name as its agent shows it: Claude Code's title
// (the one the user set, else the one it generated), Codex's thread
// name, Cursor's conversation name; else the session's first prompt.
// One line, masked as transcripts are. Empty only when the agent's
// files cannot be read.
Title string `json:"title,omitempty"`
// Summary is the opening of the session's latest compaction summary (its
// first section), one line, masked; absent until the agent compacted the
// session. The transcript route serves the whole summary.
Summary string `json:"summary,omitempty"`
// Activity is what the transcript says the session is doing: its latest
// tool calls, how many files it wrote and its latest message (see
// Activity). Absent for Cursor sessions and until a transcript has a tool
// call or assistant text.
Activity *Activity `json:"activity,omitempty"`
// Endable is whether the app may end the session (end.go): it waits on
// its user or went quiet, and baum can stop its agent.
Endable bool `json:"endable,omitempty"`
// Messageable is whether the app may send the session a message
// (message.go): it waits at its prompt, and baum can reach its agent.
Messageable bool `json:"messageable,omitempty"`
// Focus is how precisely the focus route can bring the session forward:
// "session" for its own tab, pane or conversation, "window" for the
// window or application it runs in. Absent when it cannot, as for a
// session that ended or whose hooks did not say where it runs.
Focus string `json:"focus,omitempty"`
}
Listed is a session as the list serves it: the tracker's Session, the title its agent gave it and, after a compaction, what the summary says it is about.
type Location ¶
type Location struct {
Vendor vendors.Slug
// ID is the session's id: Cursor's conversation.
ID string
// contains filtered or unexported fields
}
Location is where a session's transcript is read from: files of an agent's store, or one conversation of Cursor's database (Locate).
func Locate ¶
Locate finds the files of the transcript of the agent's session id, whose hooks named path (empty when none did), and checks that they are the agent's own: inside its session store under home, its symbolic links resolved. A Codex session without a path is looked up in its store by id; a Cursor session is read from cursorStore. Errors are *Unavailable.
type RecordedLinker ¶
RecordedLinker resolves a saved Git observation without reading today's checkout.
type Resolver ¶
type Resolver struct {
Registry Registry
Discovery Discovery
// Now is the clock; nil is time.Now.
Now func() time.Time
// contains filtered or unexported fields
}
Resolver is the Linker the daemon uses. It follows the existing linkage and adds nothing to it: the directory's clone is the registered clone sharing its git directory (a linked worktree resolves to its clone, as it does for session linkage), the branch is what git has checked out, and the pull request is the open one of the discovery cache whose head is that branch. A cached merged or closed PR can still match the exact same-repository head. It never contacts GitHub and never fetches.
func (*Resolver) LinkCommit ¶
LinkCommit implements WorkLinker: the commit head made on branch in the working tree dir, with the remotes the clone has (prlink.At), not what dir has checked out since. It runs git and never fetches.
type Sender ¶
Sender puts a message the user wrote in the app into one of its agent's sessions, by id, as a new turn of the same conversation, shown in the session's own terminal. It returns ErrAgentGone when no process of the session is running.
type Session ¶
type Session struct {
Agent string `json:"agent"`
ID string `json:"session_id"`
State string `json:"state"`
// StartedAt is the session's start event, or its first event the
// daemon saw when it did not see the start.
StartedAt time.Time `json:"started_at"`
LastActivityAt time.Time `json:"last_activity_at"`
// LastPromptAt is the latest prompt event, absent before the daemon saw
// one: what the Sessions page orders running sessions by, as their
// activity moves with each tool call.
LastPromptAt time.Time `json:"last_prompt_at,omitzero"`
// LastEvent is the normalised kind of the latest event (EventStart,
// EventPrompt, EventTool, EventWait, EventStop or EventEnd).
LastEvent string `json:"last_event"`
// WaitingReason is why the session waits on its user (WaitPermission,
// WaitInput or WaitTurnFinished), absent while it works, once it ended,
// and for a session that is only silent.
WaitingReason string `json:"waiting_reason,omitempty"`
// WaitingSince is when that wait began.
WaitingSince time.Time `json:"waiting_since,omitzero"`
// WaitingMessage is the line a Claude Code notification showed for the
// wait, such as "Claude needs your permission to use Bash": format and
// control characters dropped, masked as transcripts are and at most
// maxWaitingMessage characters. Absent when the agent sent none.
WaitingMessage string `json:"waiting_message,omitempty"`
// EndedAt is when an ended session sent its end event, absent otherwise.
EndedAt time.Time `json:"ended_at,omitzero"`
Repository string `json:"repository"`
Branch string `json:"branch,omitempty"`
// PR is the pull request whose head is Branch, 0 when there is none
// (yet) or it cannot be told.
PR int `json:"pr,omitempty"`
PRState string `json:"pr_state,omitempty"`
HeadSHA string `json:"head_sha,omitempty"`
Upstream string `json:"upstream,omitempty"`
HeadRepository string `json:"head_repository,omitempty"`
UpstreamRepository string `json:"upstream_repository,omitempty"`
LinkMethod string `json:"link_method,omitempty"`
LinkStatus string `json:"link_status,omitempty"`
ObservedAt time.Time `json:"observed_at,omitzero"`
WorkHistory *WorkHistory `json:"work_history,omitempty"`
}
Session is what the application is told of a tracked session.
type Source ¶
type Source interface {
Recent(limit int) ([]hookevents.Record, error)
SubscribeCounted(buffer int, match func(hookevents.Event) bool) (events <-chan hookevents.Record, cancel func(), dropped func() uint64)
}
Source is the daemon's record of hook events; *hookevents.Store is one.
type Step ¶
type Step struct {
Tool string `json:"tool"`
Target string `json:"target,omitempty"`
At string `json:"at,omitempty"`
}
Step is one tool call: the tool as the transcript names it, normalised for display, and what it was aimed at, masked, on one line and cut, with the time of the transcript entry when it has one (RFC 3339, UTC).
type Tracker ¶
type Tracker struct {
Source Source
Linker Linker
// Notify is called, coalesced to at most one call per NotifyEvery, after
// the sessions changed. It must not block.
Notify func()
Log *slog.Logger
// Now is the clock; nil is time.Now.
Now func() time.Time
// StateDir, when set, is a private directory where live and ended
// observations are kept across restarts (StateFile): no local paths or
// notification message text. An open one silent for DropAfter is restored
// only while Alive says its agent runs.
StateDir string
// Alive, when set, reports whether a session's agent still runs: true
// only for a process it confirms is the session's. A session silent for
// DropAfter stays listed while it does, as one waiting for its user may be
// answered hours later; nil drops it then.
Alive func(agent, id string) bool
// Buffer is the subscription's size; zero is 256. A subscriber that falls
// behind misses events, which Reconcile applies from the record once the
// subscription reports it missed some.
Buffer int
// Mask, when set, masks the secrets and e-mail addresses in a waiting
// message, as transcripts are masked; nil keeps the text.
Mask func(string) string
// Waiting, when set, is called once for each wait for a permission or an
// answer (WaitPermission, WaitInput) of a session the application is
// shown, as it begins: not for a finished turn, not for the waits a
// rebuild replays when the daemon starts, and not for one that began more
// than AnnounceWithin ago. It is called without the tracker's lock and
// must not block.
Waiting func(Session)
// contains filtered or unexported fields
}
Tracker follows the sessions of the agents whose hooks report to the daemon.
func (*Tracker) Answered ¶
Answered records that the app answered the permission or question a session waited for, at at: it works again, though the agent reports nothing until the tool call ends. A session with an event newer than at, or that waits for nothing else, is left as it is. It reports whether the session changed.
func (*Tracker) Apply ¶
Apply updates the sessions with a hook event and reports whether what the application sees changed enough to tell it. It never waits for git: a new session is hidden until ResolvePending links it.
func (*Tracker) Gone ¶
Gone records that a session's agent no longer runs, found at at: it ended, as its end event would have said had it reached the daemon. An ended session is left as it is. It reports whether the session changed.
func (*Tracker) Handler ¶
Handler serves GET /api/live-sessions: {"sessions": [...]} with the Session of each tracked session, the most recently active first. The daemon mounts it behind the same authentication as every application route.
func (*Tracker) Interrupted ¶
Interrupted records that a session's user turned down, in the terminal, the permission or answer it waited for, at at: Claude Code interrupts the turn and sends no stop, so the session waits for its user's next prompt, as at the end of a turn. A session with an event newer than at, or that waits for nothing else, is left as it is. It reports whether the session changed.
func (*Tracker) Lookup ¶
Lookup is a session the application is shown, with the transcript path its hooks last reported (empty when none did, or for an ended session read back from StateFile, which keeps no paths); false when the tracker does not list it.
func (*Tracker) Rebuild ¶
Rebuild replaces the sessions with those the record's events show, so a daemon that restarts keeps showing the sessions that were running. Their links are looked up afterwards, off the caller's goroutine when Run is the caller; ResolvePending does it for a caller without one.
func (*Tracker) Reconcile ¶
Reconcile applies the events of the record that the subscription did not deliver (a full buffer drops them) and reports whether what the application sees changed. Applying an event twice changes nothing, so it can run at any time.
func (*Tracker) ResolvePending ¶
ResolvePending looks up the link of each session waiting for one and reports whether what the application sees changed. Run does it in the background after every event that needs it.
func (*Tracker) Resumed ¶
Resumed records that a session's Stop hook handed it a message the app queued, at at: the agent goes on with the turn the stop ended, though it may report nothing until it calls a tool or stops again. A stop the tracker records after this, but which happened before at, is the one that delivered the message and is ignored as old news. A session with an event newer than at, or that waits for its user, is left as it is. It reports whether the session changed.
func (*Tracker) Run ¶
Run rebuilds the sessions from the record, then follows new events until ctx ends.
type Transcripts ¶
type Transcripts struct {
Tracker *Tracker
// Render writes a session's transcript; transcript.Renderer.Render is
// the one every local transcript is written with.
Render func(ctx context.Context, graph *session.Graph, meta transcript.Meta) ([]byte, error)
// Home holds the agents' session stores; empty is the user's home.
Home string
// CursorStore is Cursor's database; empty is its default global store.
CursorStore string
// Now is the clock; nil is time.Now.
Now func() time.Time
Log *slog.Logger
// Enders stop a session's agent, by agent (end.go); an agent without
// one cannot be ended from the app.
Enders map[string]Ender
// Senders put a message from the app into a session, by agent
// (message.go); an agent without one cannot be messaged.
Senders map[string]Sender
// Focus brings a running session forward; nil serves no focus route and
// lists no session as one it can bring forward.
Focus Focuser
// contains filtered or unexported fields
}
Transcripts serves the transcript of a session the Tracker lists, rendered as the review page renders a linked session's, while it runs.
The file read is never named by a request: it is the transcript path the session's hooks reported, and it is read only when, its symbolic links resolved, it is a regular file inside that agent's own session store under the home directory (~/.claude/projects for Claude Code, ~/.codex/sessions for Codex). A Codex session whose hooks named no path is looked up in that store by its id; a Cursor session is read from Cursor's own database by its conversation id, since Cursor keeps no transcript file baum reads.
func (*Transcripts) Get ¶
Get answers with the transcript of the tracked session agent/id. An error is ErrNotTracked, an *Unavailable, or the context's.
func (*Transcripts) Handler ¶
func (t *Transcripts) Handler() http.Handler
Handler serves the tracker's list as Tracker.Handler does, each session with its title (Listed), and GET /api/live-sessions/{agent}/{id}/transcript: the transcript of a session the list has, for as long as it has it. The answer is {"available":true,"version","live","transcript","summary"?,"images"?}, the transcript as GET /api/sessions/{id} answers it, the session's latest compaction summary (masked) and the media types of the images pasted with each prompt, by its turn, all left out when the request names the current version (?version=), so a window polling a session that did not change receives a few bytes; or {"available":false,"live", "code":"transcript_unavailable","message"} with why it is not served. A session the tracker does not list is 404.
GET /api/live-sessions/{agent}/{id}/transcript/images/{turn}/{n} is the n-th of those images (from 0), as it was pasted: images are not masked. One the transcript does not hold is 404.
func (*Transcripts) Image ¶
func (t *Transcripts) Image(ctx context.Context, agent, id, turn string, n int) (string, []byte, error)
Image is the n-th image (from 0) pasted with the prompt turn of the tracked session agent/id, and its media type, read from the session's transcript as Get reads it. An error is ErrNotTracked, an *Unavailable, ErrNoImage, or the context's.
func (*Transcripts) Read ¶
Read answers with the transcript of the session agent/id whether the tracker lists it or not, such as one that ended long ago: from path, the transcript its hooks named when it was captured (empty when none did), on branch. While the tracker lists it, its session and path are the tracker's. The file read is held to the same rules as Get's. An error is an *Unavailable or the context's.
func (*Transcripts) Watch ¶
func (t *Transcripts) Watch(ctx context.Context, notify func())
Watch calls notify, until ctx ends, when what the list serves changed with no hook event to say so: agents report few events during a turn, so a running session's steps, files and message, and a silent session settling (settle), are only in its transcript. The list is built every WorkingRecheckEvery, which reads a working session's files when they grew (only what they appended) and the others' no more often than describe allows.
func (*Transcripts) WriteAnswer ¶
func (t *Transcripts) WriteAnswer(w http.ResponseWriter, r *http.Request, answer Answer, err error)
WriteAnswer writes a transcript answer as the live sessions' transcript route does (Handler), for any route that serves one: the summary is the tracked session's.
type Unavailable ¶
Unavailable is a tracked session whose transcript is not served, with the reason, a sentence the application shows.
func (*Unavailable) Error ¶
func (u *Unavailable) Error() string
type WorkHistory ¶
type WorkHistory struct {
Entries []WorkObservation `json:"entries"`
}
type WorkLinker ¶
type WorkLinker interface {
// LinkCommit links the commit head made on branch ("" when detached) in
// the working tree dir, from what the hook recorded rather than what dir
// has checked out now, and reports false when dir is in no registered
// clone.
LinkCommit(ctx context.Context, dir, branch, head string) (Link, bool)
// LinkPush links a push from the working tree dir through remote, of
// head to branch, and reports false when dir is in no registered clone.
LinkPush(ctx context.Context, dir, remote, branch, head string) (Link, bool)
}
WorkLinker links the commits and pushes of a session's agent; *Resolver is one. With a Linker that is not one, they link nothing.
type WorkObservation ¶
type WorkObservation struct {
At time.Time `json:"at"`
HeadRepository string `json:"head_repository,omitempty"`
UpstreamRepository string `json:"upstream_repository,omitempty"`
Repository string `json:"repository"`
Branch string `json:"branch,omitempty"`
HeadSHA string `json:"head_sha,omitempty"`
Upstream string `json:"upstream,omitempty"`
PR int `json:"pr,omitempty"`
}
WorkObservation is recorded Git context, not proof of code contribution.