Documentation
¶
Overview ¶
Package console owns the record of a chat: pi's session files (console.md §5a: terminal = presentation, session files = record) read through one parser, and the transcript projected from them as chat bubbles, with the hand-back signal and the submit verification the runtime relies on. The containers, the steering and the HTTP doors stay in internal/apptown; the words are read here, the same way whether the session is live or long ended.
Index ¶
- Constants
- Variables
- func HelperErrorText(raw string) string
- func LocalTLSPrelude(t LocalTLS) string
- func ToolchainArgs(argv []string) []string
- func ValidPictureName(name string) bool
- type Agent
- type Attachment
- type CheckoutEdit
- type Config
- type Conversation
- type Dependencies
- type Ended
- type Entry
- type Environment
- type File
- type Launch
- type LocalTLS
- type Match
- type Message
- type MessagePage
- type Meta
- type NoLocal
- func (NoLocal) Attach(context.Context, string, Size) (Terminal, error)
- func (NoLocal) Deliver(context.Context, Launch, string) error
- func (NoLocal) End(context.Context, string) error
- func (NoLocal) Interrupt(context.Context, string) error
- func (NoLocal) Reap(context.Context, map[string]bool) error
- func (NoLocal) Remove(context.Context, string) error
- func (NoLocal) Stale(context.Context, Launch) (bool, error)
- func (NoLocal) Start(context.Context, Launch) error
- func (NoLocal) Status(context.Context, string) (Status, error)
- func (NoLocal) Stop(context.Context, string) error
- type OpenRequest
- type Placed
- func (p Placed) Attach(ctx context.Context, id string, size Size) (Terminal, error)
- func (p Placed) Deliver(ctx context.Context, in Launch, text string) error
- func (p Placed) End(ctx context.Context, id string) error
- func (p Placed) Interrupt(ctx context.Context, id string) error
- func (p Placed) Reap(ctx context.Context, alive map[string]bool) error
- func (p Placed) Remove(ctx context.Context, id string) error
- func (p Placed) Stale(ctx context.Context, in Launch) (bool, error)
- func (p Placed) Start(ctx context.Context, in Launch) error
- func (p Placed) Status(ctx context.Context, id string) (Status, error)
- func (p Placed) Stop(ctx context.Context, id string) error
- type Provision
- type Reader
- type Recording
- type RelocationRecord
- type Resumption
- type RuntimeIdentity
- type Service
- func (s *Service) Annotate(ctx context.Context, id, key, value string) error
- func (s *Service) Attach(ctx context.Context, id string, size Size) (Terminal, error)
- func (s *Service) Block(key, value string) (func(), error)
- func (s *Service) Busy(ctx context.Context, m Match, within time.Duration) bool
- func (s *Service) End(ctx context.Context, id string) (Ended, error)
- func (s *Service) Ended(ctx context.Context, id string) (bool, error)
- func (s *Service) Evict(ctx context.Context, id string) error
- func (s *Service) Get(ctx context.Context, id string) (*Session, error)
- func (s *Service) Gone(ctx context.Context, id string) (bool, error)
- func (s *Service) Hold(id string) func()
- func (s *Service) Identity(token string) (RuntimeIdentity, bool)
- func (s *Service) Interrupt(ctx context.Context, id string) error
- func (s *Service) List(ctx context.Context, m Match) []*Session
- func (s *Service) Messages(ctx context.Context, id string, from int) (MessagePage, error)
- func (s *Service) NewSessionID() string
- func (s *Service) Open(ctx context.Context, in OpenRequest) (*Session, error)
- func (s *Service) Recorded(id string) (Meta, error)
- func (s *Service) RecordedPicture(scope, id, name string) (string, error)
- func (s *Service) Records(scope string) ([]Recording, []string, error)
- func (s *Service) RecordsMatching(m Match) (records []Recording, unreadable []string, err error)
- func (s *Service) Remove(ctx context.Context, id string) error
- func (s *Service) RemoveEnded(m Match) error
- func (s *Service) Reserve(labels map[string]string) (func(), error)
- func (s *Service) Restore(meta Meta) *Session
- func (s *Service) Resume(ctx context.Context) error
- func (s *Service) Send(ctx context.Context, id, text string) error
- func (s *Service) Shutdown(ctx context.Context) error
- func (s *Service) State(ctx context.Context, sess *Session) State
- func (s *Service) Stop(ctx context.Context, id string) error
- func (s *Service) Transcript(ctx context.Context, scope string) (convs []Conversation, unreadable []string, err error)
- func (s *Service) Upload(ctx context.Context, id string, f File) (Attachment, error)
- func (s *Service) UploadPath(ctx context.Context, id, name string) (string, error)
- func (s *Service) UsesWorkspace(ctx context.Context, workspace string) bool
- func (s *Service) Warm(ctx context.Context, id string) error
- func (s *Service) Watch(ctx context.Context, id string) (Terminal, error)
- type Session
- func (s *Session) Active(now time.Time, within time.Duration) bool
- func (s *Session) Activity() Turn
- func (s *Session) Done() <-chan struct{}
- func (s *Session) HandedBack() bool
- func (s *Session) Idle(now time.Time, limit time.Duration) bool
- func (s *Session) LastUsed() time.Time
- func (s *Session) LatestRecord() (string, int64)
- func (s *Session) Messages() []Message
- func (s *Session) Persist() error
- func (s *Session) ReadMessages() ([]Message, error)
- func (s *Session) RecordOffset() int64
- func (s *Session) Records() ([]string, error)
- func (s *Session) SetLastUsed(t time.Time)
- func (s *Session) Steering() string
- func (s *Session) Touch()
- func (s *Session) Turn() Turn
- func (s *Session) UserMessageAppended(file string, off int64) bool
- func (s *Session) UsesWorkspace(workspace string) bool
- func (s *Session) Viewers() int
- func (s *Session) Workspace() Workspace
- type Size
- type Staged
- type State
- type Status
- type Terminal
- type ToolCall
- type Transcript
- func (t *Transcript) LatestSessionFile() (string, int64)
- func (t *Transcript) Messages() []Message
- func (t *Transcript) Offset() int64
- func (t *Transcript) ReadMessages() ([]Message, error)
- func (t *Transcript) SessionFiles() ([]string, error)
- func (t *Transcript) TurnHandedBack() bool
- func (t *Transcript) UserMessageAppended(file string, off int64) bool
- type Turn
- type Usage
- type Workspace
Constants ¶
const ( WorkMount = "/work" // the checkout (a change's worktree for a change console) Mount = "/console" // the session dir: pi-home, sessions, scratch, steering.md SessionsMount = Mount + "/sessions" // pi --session-dir UploadsMount = Mount + "/uploads" // pasted pictures ScratchMount = Mount + "/scratch" // throwaway notes and experiments TmuxSession = "console" // tmux -s / -t AgentSession = "console" // the agent's session id, unless another agent's session is resumed MetaFile = "session.json" // present = alive EndedFile = "ended.json" // the record after End: skipped by recovery, read by the transcript SteeringFile = "steering.md" // the prompt the agent reads: authored text, the provision's notes, the notices AuthoredFile = "steering.authored.md" // the authored part, as handed to Open NoticesFile = "notices.md" // what the console told the agent since: resumes StagedFile = "staged.json" // the files the last provision placed, for replacement RehearsedFile = "rehearsed-fingerprint" // the change console's boot-rehearsal memory SavedFile = "saved.json" // the opener's record of the chat's last save to its branch )
The container's view of a session, and the names pi and tmux are pinned to. Every path the steering promises and the runtime writes comes from here, so a mount cannot move without the prose and the code moving too.
Variables ¶
var ( ErrNoSession = fault.New(fault.NotFound, "no such session") ErrEnded = errors.New("console session is stopped") // ErrAsleep: watching a terminal never starts the agent. ErrAsleep = fault.New(fault.Conflict, "this chat is not running; it starts again when someone who may work on it says something") // ErrWatching: a watcher's terminal takes no keystrokes. ErrWatching = fault.New(fault.Forbidden, "this terminal is open to watch, not to type") )
var ErrNoLocalChats = errors.New("this chat ran on AppTown, which no longer runs chats")
ErrNoLocalChats refuses running a chat on an AppTown that runs none.
var ErrNoPicture = errors.New("no such picture")
ErrNoPicture: the chat has no picture by that name, or the name is not one the console gives.
var ToolchainPrelude = toolchainPrelude("/work", "/tmp")
ToolchainPrelude installs the workspace's declared toolchain (mise.toml) before a session starts, wherever it runs (AppTown's own Docker or a worker's), so `python`, `gofmt`, `node` are on PATH through the per-workspace shims without the agent having to know about mise (complaints #50, #59: the documented commands were "not found" until someone ran `mise install`). A failed install no longer passes silently: a session whose Ruby never installed went on to hand-write a Rails skeleton, and nobody saw why. The installer's output is kept in the container's /tmp, a failure is said on stderr (the step's log, a chat's terminal) with the output's last lines, and the session still starts with whatever toolchains the image has.
Functions ¶
func HelperErrorText ¶
HelperErrorText turns a provider's error into a sentence a person can act on. Provider JSON ({"error":{"message":…}}) is unwrapped; anything else is shown trimmed.
func LocalTLSPrelude ¶
LocalTLSPrelude runs in a container before its command (a chat's agent, a capture of a preview), when AppTown has a local authority. Every client resolves *.localhost to the container's own loopback itself, before any hosts file, so the prelude leads that loopback's public port to AppTown: the TLS stream passes through untouched and the preview's name arrives as SNI and Host, as from a browser. Trusting the authority is not its job: it is in the extra certificates every container AppTown starts here is given (internal/trust, trust.Run), which the container runs first. Everything here fails soft: a chat starts even if a tool is missing.
func ToolchainArgs ¶
ToolchainArgs wraps an argv so the prep runs first and the command then replaces the shell (signals and exit status pass straight through). The argv's last word is the agent's prompt: when the install failed, the agent is told so after it (toolchainInstallNote).
func ValidPictureName ¶
ValidPictureName reports whether name is one Upload gives a pasted picture: never a path.
Types ¶
type Agent ¶
type Agent interface {
// Start makes the agent run for the launch: create it if absent, start
// it if stopped, leave it if running.
Start(ctx context.Context, in Launch) error
// Stale reports that what runs no longer matches the launch (the image,
// the model or the Docker mode moved), so it should be rebuilt when safe.
Stale(ctx context.Context, in Launch) (bool, error)
// Status is the runtime's view: does it exist, is it running, is a
// terminal held on it.
Status(ctx context.Context, id string) (Status, error)
// Deliver types text at the agent and presses enter, verifying against
// the session record that the words entered the conversation.
Deliver(ctx context.Context, in Launch, text string) error
// Interrupt stops the agent's current turn; the conversation stays.
Interrupt(ctx context.Context, id string) error
// Attach opens a terminal on the agent's screen, recent output replayed.
Attach(ctx context.Context, id string, size Size) (Terminal, error)
// Stop stops the agent's process; its filesystem and the session dir stay.
Stop(ctx context.Context, id string) error
// Remove takes the agent's runtime away entirely: process, sidecar,
// networks. The session stays; the next Start builds a new runtime.
Remove(ctx context.Context, id string) error
// End takes the runtime away for good: the session has ended and its
// runtime is never started again. For a runtime on this machine it is
// Remove; a runtime on a worker also fences the session there.
End(ctx context.Context, id string) error
// Reap removes runtime left behind by sessions that no longer exist.
Reap(ctx context.Context, alive map[string]bool) error
}
Agent is the runtime a session's agent runs in: pi in a container under tmux for real, a recorder in tests. The console never shells out; every process, terminal and sidecar is behind this interface.
type Attachment ¶
type CheckoutEdit ¶
type CheckoutEdit struct {
File string `json:"file"`
Before string `json:"before"`
After string `json:"after"`
BeforeDigest string `json:"beforeDigest"`
AfterDigest string `json:"afterDigest"`
}
CheckoutEdit holds only paths and content digests, never session credentials. It can be included in a relocation journal and retried after a crash.
func (CheckoutEdit) Apply ¶
func (e CheckoutEdit) Apply() error
Apply changes only checkout, preserving ownership, credentials and unknown fields. Exact old/new digests make replay safe; a third state refuses.
type Config ¶
type Config struct {
DataDir string // session records live at <DataDir>/console/<id>
NoDind bool // change consoles get no private Docker daemon
// Mirror answers the Docker Hub mirror a private daemon and its
// builder pull through ("" none), read at each launch. Nil is none.
Mirror func(context.Context) (string, error)
// Now is the console's clock: a session's last use and the idle and
// busy rules judged against it, and the times its records carry. Nil
// is the real clock.
Now func() time.Time
}
Config is the console's own settings.
type Conversation ¶
type Conversation struct {
ID string `json:"id"`
CreatedAt time.Time `json:"createdAt"`
Actor string `json:"actor"`
Messages []Message `json:"messages"`
// Ended: the session's record is its archive (EndedFile), not a live one.
Ended bool `json:"-"`
// Labels are the opener's, handed back unread, as on Meta.
Labels map[string]string `json:"-"`
}
Conversation is one session's words, for a change's or a project's chat history.
type Dependencies ¶
type Dependencies struct {
Agent Agent // the runtime the agent runs in
Environment Environment // the provision for a launch (model, env, mounts); nil runs sessions as opened
// Settle is what an end does with the session's last record before the
// session is archived or removed (the opener's accounting of it). It runs
// after the agent's runtime has ended, so the runtime's last record is
// in; an error keeps the session registered and its record in place. Nil
// settles nothing.
Settle func(ctx context.Context, s *Session) error
Config Config
}
Dependencies is what Console needs and does not own.
type Ended ¶
type Ended struct {
Session *Session
}
Ended is what End leaves for the caller to finish: a change console's worktree is the change's and stays; a project console's snapshot is deleted here.
type Entry ¶
type Entry struct {
Type string // "session" | "model_change" | "message" | ...
At time.Time // the line's timestamp; zero when absent or unparsable
Role string // message entries: user | assistant | toolResult
Provider string // assistant entries
Model string
Usage Usage
Text string // the text parts, joined by newlines (thinking excluded)
Tools []ToolCall
// An assistant entry that ended in an error (the model call failed: no
// credit, a bad key, a provider outage): stopReason "error" and the raw
// provider message. Without surfacing this the chat looks busy forever.
StopReason string
ErrorMessage string
}
Entry is one line of the record, reduced to what AppTown reads. One JSON object per line under pi's session directory: a "session" header, then "model_change" / "thinking_level_change" marks and "message" entries whose role is user, assistant or toolResult.
func ParseLine ¶
ParseLine decodes one record line. Lines that are not JSON objects (blank, corrupt) are skipped, not errors: the record is append-only and a bad line is pi's, not ours.
type Environment ¶
Environment answers what a session's launch gets, at every wake, because the answer moves (a model follows a slot, a binding is renamed). The system that opened the console implements it.
type File ¶
Upload stores a pasted picture under the session, server-named, and says where the agent will find it.
type Launch ¶
type Launch struct {
ID string
Dir string // the session dir, mounted at Mount
Actor string
Token string
Workspace Workspace // mounted at WorkMount
AgentSession string // the agent's session id to resume
Model string
Docker bool // a private Docker daemon for the agent's own builds
// Mirror is the Docker Hub mirror that daemon and its builder pull
// through ("" none): data from AppTown, on its machine or a worker's.
Mirror string
Env []string
Mounts []string
// Image is the agent image to create from; empty means the runtime's
// own. A worker runs the image its AppTown names.
Image string
}
Launch is everything the runtime needs to run a session's agent. The console fills the session facts; the environment (a dependency) supplies the provision: the model, the env, the mounts, and the staged home.
type LocalTLS ¶
type LocalTLS struct {
CACert []byte // the authority's certificate (PEM), never its key: trusted for deployments (internal/trust)
Port int // the public port, the one in every https://….localhost:<port> address
Target string // where that port really is from the container: host.docker.internal:<apptownd's port>
// PreviewDomain is the preview tier's domain (preview.localhost). Tools
// that ask the system resolver (Python, Node), unlike curl and
// browsers, do not answer *.localhost names themselves, so a change
// chat's own preview name is written into its hosts file.
PreviewDomain string
}
LocalTLS is an AppTown serving itself over HTTPS from its own local certificate authority (apptownd -local-tls, the desktop trial). A chat on that AppTown trusts the authority and reaches AppTown's HTTPS addresses (its change's preview) at their public URLs.
type Message ¶
type Message struct {
I int `json:"i"`
Role string `json:"role"` // "you" | "helper"
Text string `json:"text,omitempty"`
Tools []struct {
Name string `json:"name"`
Detail string `json:"detail,omitempty"`
} `json:"tools,omitempty"`
At string `json:"at,omitempty"`
// Error: the helper's turn ended in a model error; Text says what and
// what to do. The chat must never look busy over a helper that has
// stopped.
Error bool `json:"error,omitempty"`
// Stopped: the person pressed stop and the helper aborted its turn.
Stopped bool `json:"stopped,omitempty"`
}
Message is one chat bubble: a projection of the record at story zoom. The terminal remains one toggle away as the forensic layer; there is no second rendering that could drift.
type MessagePage ¶
MessagePage is the conversation from a bubble index on, with the turn state.
type Meta ¶
type Meta struct {
ID string `json:"id"`
// Scope is what the opener groups sessions by ("project:56",
// "change:305"); List, Transcript and Identity answer in its terms.
Scope string `json:"scope"`
// Labels are the opener's facts about the session, kept on the record
// and returned with its identity. The console reads none of them.
Labels map[string]string `json:"labels,omitempty"`
Model string `json:"model"`
// ModelPinned: the person named this model when opening the chat. An
// unpinned session follows whatever the environment answers at wake.
ModelPinned bool `json:"modelPinned,omitempty"`
Actor string `json:"actor"`
CreatedAt time.Time `json:"createdAt"`
// AgentSession is the agent's own session id the runtime resumes
// ("console" by default). Resuming another agent's session sets it to
// that agent's, so the chat continues the agent that did the work.
AgentSession string `json:"agentSession,omitempty"`
// ResumedFrom: plain words for the header when another agent's session
// was resumed.
ResumedFrom string `json:"resumedFrom,omitempty"`
// Token is the one credential the agent inside holds: it scopes what
// the agent may do to its own session. It lives in the container env
// and this record, never in a UI.
Token string `json:"token"`
// Checkout is the host path mounted at WorkMount. OwnsCheckout says the
// console made it and deletes it with the session; otherwise it is
// borrowed (a worktree) and stays. Writable says how it is mounted.
Checkout string `json:"checkout,omitempty"`
OwnsCheckout bool `json:"ownsCheckout,omitempty"`
Writable bool `json:"writable,omitempty"`
// Docker: the agent gets a private Docker daemon for its own builds.
Docker bool `json:"docker,omitempty"`
// LastUsed is the activity the server last saw when the record was
// written; persisted so a restart does not age an active conversation
// back to its birth.
LastUsed time.Time `json:"lastUsed,omitempty"`
}
Meta is the session's record on disk: the durable identity, enough to rebuild the session after a restart. It is the ONLY shape written to disk and read back; runtime state lives on Session and in the agent runtime. The console knows nothing of what a session is for: Scope and Labels are the opener's, kept and handed back.
type NoLocal ¶
type NoLocal struct{}
NoLocal is the local runtime of an AppTown that runs no chats (-no-local-docker). That AppTown's start was refused while a chat placed on it had not ended, so nothing of one is here: its status is absent, and stopping, removing, ending and reaping have nothing to do. Waking one is refused (ErrNoLocalChats); its record and transcript stay.
type OpenRequest ¶
type OpenRequest struct {
// ID is the session's id, from NewSessionID, when the opener must name
// the session before it exists (to take a change for it); empty picks
// one.
ID string
Scope string // what the opener groups sessions by
Labels map[string]string // the opener's facts, kept and handed back
Workspace Workspace // the checkout to mount; the caller made it
Docker bool // a private Docker daemon for the agent's own builds
Steering string // the authored prompt
// Files are placed in the session dir once, at open, and kept for the
// session's life beside the authored prompt, which may name them (the
// guidance's skills): a restart keeps them as it keeps the prompt.
Files map[string]Staged
Actor string
Model string // the model the chat opens on
Pinned bool // the person named it; otherwise the environment's answer wins
Resume *Resumption
}
OpenRequest is everything a session needs; it looks nothing up.
type Placed ¶
type Placed struct {
Local, Remote Agent
// OnRemote reports whether a session's record places it on the remote
// runtime; an error is unknown, and the call fails rather than asking
// the wrong runtime.
OnRemote func(id string) (bool, error)
}
Placed runs each session's agent on one of two runtimes by its recorded placement: a chat on a worker has its runtime there (Remote), every other on this machine (Local). The placement is read from the session's record, live or ended, so an ended chat's runtime is still asked where it ran: only the runtime a chat ran on can say it is gone.
type Provision ¶
type Provision struct {
Model string
Env []string
Mounts []string
Files map[string]Staged // path under the session dir → what to put there
Notes string // prose about the provision, refreshed each wake
}
Provision is what the world outside supplies for a launch: the model, the container's environment and mounts, files staged into the session dir (the agent's settings, keys, a binary), and words about what was provisioned, which the console puts into the prompt. At the bottom an agent gets a model name, environment variables, mounted files and one block of text; this is that, and nothing more.
type Reader ¶
Reader reads a record incrementally. Off is the byte offset of the first unread complete line; a trailing partial line (pi mid-write) is left for the next call. Zero value plus a path reads from the start.
func (*Reader) Next ¶
Next returns the entries in the complete lines appended since the last call (all of them on the first). A record shorter than the offset (a rewrite, not an append) is read again from the start.
The agent writes its record, so the reader trusts nothing about it: it reads only a regular file, opened through a root on the record's directory so a link cannot lead it elsewhere, up to the size it had when opened, and it skips a line longer than maxLine without holding it.
type Recording ¶
Recording is a session's record on disk, live or ended, with its directory, whether it is live in this process, and whether its record is its archive (it ended).
func (Recording) ReadMessages ¶
ReadMessages are the recorded session's words, read from its record; a record that cannot be read is an error, never no words.
type RelocationRecord ¶
RelocationRecord is a strictly read live session record. Ended records are historical evidence and remain untouched. This never resumes or reaps agents.
func RelocationRecords ¶
func RelocationRecords(dataDir string) ([]RelocationRecord, error)
RelocationRecords returns the complete readable inventory or an error. An unreadable record is not an absent session and cannot be silently omitted.
func (RelocationRecord) CheckoutEdit ¶
func (r RelocationRecord) CheckoutEdit(to string) (CheckoutEdit, error)
func (RelocationRecord) RefuseWriter ¶
func (r RelocationRecord) RefuseWriter(ctx context.Context, agent Agent) error
RefuseWriter observes the full runtime status. An absent primary container does not prove a private daemon or workspace script stopped writing.
type Resumption ¶
type Resumption struct {
// Record is the other agent's record itself, as its owner kept it, for
// the session Session; it is written as that session's file. Empty
// starts a fresh agent.
Record []byte
Session string
Label string // plain words for the header
}
Resumption continues another agent's session: its record file is copied in and resumed, and the header says so.
type RuntimeIdentity ¶
RuntimeIdentity is who the token's holder is: the agent inside a session, with the opener's scope and facts.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service owns the sessions: their records on disk, the registry in memory, and every operation on one. Built once.
func New ¶
func New(deps Dependencies) *Service
func (*Service) Attach ¶
Attach opens a terminal on the session, waking the agent first. The terminal counts as a viewer until it is closed.
func (*Service) Block ¶
Block admits the removal of everything labelled key=value (a project being deleted): refused while such a chat is being opened, is mid-conversation, or is ending. Until the release, no such session is opened, admitted or identified.
func (*Service) End ¶
End removes the session: the agent's runtime and the record. What remains under the session dir (the session files, the steering) is the archived conversation. Failed checkout removal retains that archive for retry; ending an archive retries cleanup without waking or settling it.
func (*Service) Ended ¶
Ended is Gone without removing anything: it reports whether the session is over and its runtime already absent, for a check that must change nothing.
func (*Service) Evict ¶
Evict takes the agent's runtime away and keeps the session registered: the reaper's move for a long-idle chat. The next message rebuilds it.
func (*Service) Gone ¶
Gone reports whether a session is over and nothing of it can still write its workspace: it is neither registered nor recorded as live (it ended, or never opened), and neither its agent nor its private Docker daemon exists. A runtime left behind by an end whose removal failed is removed again first; the answer is never assumed.
func (*Service) Hold ¶
Hold keeps id from counting as ended until release, whatever its record and runtime show: an opener that has claimed something for a session not yet opened (a change), or an ending that still has work to settle for it (publishing its commits). After release, Gone judges the session by its record and runtime as usual.
func (*Service) Identity ¶
func (s *Service) Identity(token string) (RuntimeIdentity, bool)
Identity resolves a session token: the credential the agent inside holds. A session on a project being deleted identifies nobody; so does an ended one.
func (*Service) Interrupt ¶
Interrupt stops the agent's current turn; the conversation and the workspace are untouched.
func (*Service) Messages ¶
Messages is the conversation from bubble `from` on. Reading keeps the session warm: an open chat page is a viewer in spirit.
func (*Service) NewSessionID ¶
NewSessionID makes a fresh session id: a UUIDv7 in its canonical text, minted from the console's clock.
func (*Service) Open ¶
Open creates a session: its directory and record, then its agent (created, not yet started; the first message or terminal starts it).
func (*Service) Recorded ¶
Recorded reads a session's record from disk, live or ended, whether or not this process has restored it: what it was opened with, including the opener's labels.
func (*Service) RecordedPicture ¶
RecordedPicture is where a pasted picture of a scope's chat lives on disk, live or ended, read from the chat's record without registering or waking it: the chat's words stay readable after it ends, and so do its pictures. The chat must belong to the scope and the name must be one Upload gives; nothing is listed.
func (*Service) Records ¶
Records is every session a scope has had, live or ended, oldest first, read from the records on disk, and the folders whose record could not be read (RecordsMatching).
func (*Service) RecordsMatching ¶
RecordsMatching is every session a Match selects, live or ended, oldest first, read from the records on disk: an ended session is selected by its archived record, without registering or waking it. A folder whose record cannot be read or parsed may belong to the Match, so it is named in unreadable, never skipped silently; the caller decides what that means for it.
func (*Service) RemoveEnded ¶
RemoveEnded deletes the folders of the ended sessions a Match selects, read from their records without registering or waking them: a deleted project's ended chats, words and pictures. A live session is left to Remove, which ends it first.
func (*Service) Reserve ¶
Reserve is the admission an Open holds while it provisions: a block on any of its labels admitted meanwhile is refused. Open takes it itself.
func (*Service) Restore ¶
Restore registers a session and writes its live record without touching its agent: what Resume finds per record, offered to tests that build sessions by hand. A registered chat always has its record (End archives it); a test whose folder cannot hold one panics here.
func (*Service) Resume ¶
Resume rebuilds the registry from the records on disk after a restart (agents survive restarts; terminals do not), then has the runtime reap what belongs to no record. The records are the only inventory of what may run, so the reap runs only when every one was read: a chat folder that cannot be listed, or a live record that cannot be read or is incomplete (kept for repair), is an error and nothing is reaped, since its agent may be one of those still running. The readable chats are registered either way. A folder with no live record is an ended chat.
func (*Service) Send ¶
Send types words at the agent, waking it first. Newlines fold to spaces: a raw newline would submit mid-thought.
func (*Service) Shutdown ¶
Shutdown ends the service's hold on every session for a process stop: terminals close, agents and records stay for the next Resume.
func (*Service) Stop ¶
Stop stops the agent and keeps everything else: the record, the files, the conversation. The next message or terminal wakes it.
func (*Service) Transcript ¶
func (s *Service) Transcript(ctx context.Context, scope string) (convs []Conversation, unreadable []string, err error)
Transcript is every conversation a scope has had, oldest first, read from the records on disk: sessions end and their runtimes go, the words stay. A chat folder whose record or words cannot be read is named in unreadable, never left out silently: its scope may be this one. One bad folder does not stop the others being read.
func (*Service) UploadPath ¶
UploadPath is where a named upload lives on disk, for serving it back.
func (*Service) UsesWorkspace ¶
UsesWorkspace: a live session's checkout is this workspace or lies under it.
func (*Service) Warm ¶
Warm has the agent running for the session without saying anything: an open chat page keeps its helper ready to answer.
type Session ¶
type Session struct {
Meta
Dir string // <data>/console/<id>
// contains filtered or unexported fields
}
Session is one conversation: its record, its directory on disk, and the runtime facts the service keeps (activity, viewers). The agent's process and terminal belong to the Agent runtime.
func (*Session) Active ¶
Active: watched, or used within the window. Purge and remediation ask this.
func (*Session) Activity ¶
Activity is the turn as another page reads it: empty before the first message, so a chat nobody has spoken in is not said to be working.
func (*Session) Done ¶
func (s *Session) Done() <-chan struct{}
Done is closed when the session ends; loops that watch a session select on it.
func (*Session) HandedBack ¶
HandedBack: the agent has finished its turn.
func (*Session) Idle ¶
Idle: nobody is watching and nothing has happened for limit. A fact about the session; who acts on it, and when, is the caller's business.
func (*Session) LatestRecord ¶
LatestRecord is the newest session file and its size: the marker delivery verification measures growth past.
func (*Session) Persist ¶
Persist writes the session's record. Callers persist when the moment is worth surviving a restart (a message delivered, a viewer gone).
func (*Session) ReadMessages ¶
ReadMessages is Messages, with the failure to read the record when there was one.
func (*Session) RecordOffset ¶
RecordOffset is the transcript reader's position in the newest record, for tests of the resume.
func (*Session) Records ¶
Records is every session file the conversation owns, oldest first: what usage accounting reads. A folder that cannot be read is an error.
func (*Session) SetLastUsed ¶
SetLastUsed sets the idle clock; recovery and tests use it.
func (*Session) UserMessageAppended ¶
UserMessageAppended: has a user message entered the record since the marker?
func (*Session) UsesWorkspace ¶
UsesWorkspace: the session's checkout is this workspace or lies under it.
type Staged ¶
type Staged struct {
Data []byte
Link string // a symbolic link's target, when set
Mode os.FileMode // 0 means 0644
}
Staged is one file the provision places: bytes, or a symbolic link.
type State ¶
type State string
State is where a session stands on the ladder attached → detached → stopped → dead.
const ( Attached State = "attached" // a terminal is open on it Detached State = "detached" // the agent runs, nobody is watching Stopped State = "stopped" // the agent is stopped; files intact; a message wakes it Dead State = "dead" // the agent runtime is gone; the next message rebuilds it // reached); what runs is unknown, never assumed gone. Unavailable State = "unavailable" )
type Status ¶
type Status struct {
Exists bool
// SidecarExists: the agent's private Docker daemon exists, which also
// mounts the workspace.
SidecarExists bool
// SidecarRunning: the private daemon runs, so work in it may be writing.
SidecarRunning bool
// ScriptExists: a script runs on the session's work tree beside the
// agent (a worker chat's save), and may be writing it.
ScriptExists bool
Running bool
EverStarted bool // it has run before: starting it again is a resume
Attached bool // the runtime holds a terminal on it
}
Status is the runtime's view of a session's agent.
type Terminal ¶
type Terminal interface {
io.ReadWriteCloser
Resize(Size) error
}
Terminal is one viewer's window onto the agent's screen: reads deliver the recent output then live bytes, writes are keystrokes.
type Transcript ¶
Transcript is one chat's record on disk, parsed and kept between polls: the reader's offset, the bubbles so far, and the last message entry (the hand-back signal). A new record file (a fresh runtime's first entry) starts it over. Dir is the session's directory; Since hides the turns from before this chat existed (a chat that continues a lane agent's own session shows only its own turns; the lane's transcript lives on the change page). Zero Since shows everything.
func (*Transcript) LatestSessionFile ¶
func (t *Transcript) LatestSessionFile() (string, int64)
LatestSessionFile returns the newest session record and its size: the before/after marker the submit path uses to verify a message landed.
func (*Transcript) Messages ¶
func (t *Transcript) Messages() []Message
Messages is the conversation as chat bubbles, oldest first: shared by the live chat poll, the change transcript and the narrative (session files outlive their containers; the words are part of the change's story).
func (*Transcript) Offset ¶
func (t *Transcript) Offset() int64
Offset is the reader's position in the newest record, for tests of the resume.
func (*Transcript) ReadMessages ¶
func (t *Transcript) ReadMessages() ([]Message, error)
ReadMessages is Messages, with the failure to read the record when there was one: a record that cannot be read is not a chat with no words.
func (*Transcript) SessionFiles ¶
func (t *Transcript) SessionFiles() ([]string, error)
SessionFiles is every record file this chat owns, oldest first: the pinned "console" session or the continued lane agent's own; names are timestamp-prefixed, so sorted order is chronological. Only regular files count: the agent writes this directory, and a link or a device there is not a record. A folder that does not exist holds no records; one that cannot be read is an error, never "no records".
func (*Transcript) TurnHandedBack ¶
func (t *Transcript) TurnHandedBack() bool
TurnHandedBack reports whether the helper has finished its turn: the latest record entry is an assistant message with text and no tool calls, and the transcript has been quiet for a few seconds. This is the same structural signal the UI's green bubble uses; the moment the helper claims "done" is exactly the moment worth verifying.
func (*Transcript) UserMessageAppended ¶
func (t *Transcript) UserMessageAppended(file string, off int64) bool
UserMessageAppended: has a user message entered the record since the (file, size) marker? A new file (a fresh runtime's first entry) counts from its start.
type Usage ¶
type Usage struct {
Input float64 `json:"input"`
Output float64 `json:"output"`
CacheRead float64 `json:"cacheRead"`
CacheWrite float64 `json:"cacheWrite"`
Total float64 `json:"totalTokens"`
Cost struct {
Total float64 `json:"total"`
} `json:"cost"`
}
Usage is what an assistant entry says it cost: the accounting ledger's source.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package consoletest is the fake agent runtime: it stands in for pi in a container, remembers what it was started with, and writes the session record the way pi does so the console reads replies out of it.
|
Package consoletest is the fake agent runtime: it stands in for pi in a container, remembers what it was started with, and writes the session record the way pi does so the console reads replies out of it. |
|
Package pi runs a console's agent for real: pi's interactive TUI inside a Docker container, under tmux, with apptownd holding a disposable tmux client over a host PTY.
|
Package pi runs a console's agent for real: pi's interactive TUI inside a Docker container, under tmux, with apptownd holding a disposable tmux client over a host PTY. |