Documentation
¶
Overview ¶
DisplayName and ShortID format fallback labels for coder sessions.
Index ¶
- Constants
- Variables
- func AssistantRunnerFor(c Coder) assistant.Runner
- func DisplayName(name, sessionID string) string
- func EnsureManagedSkills(repo SkillRepository, inst CockpitInstance) error
- func IsManagedSkill(id string) bool
- func LessSession(a, b Session) bool
- func NormalizeCWD(path string) string
- func ParseTimestamp(raw string) (time.Time, bool)
- func RemoveManagedSkills(repo SkillRepository, inst CockpitInstance) error
- func SanitizeAgentID(raw string) (string, error)
- func SanitizeSkillID(raw string) (string, error)
- func ShortID(id string) string
- type Activity
- type ActivityProfile
- type ActivityReporter
- type ActivityStamper
- type Agent
- type AgentOption
- type AgentRepository
- type AgentSaveResult
- type AssistantCapable
- type CapabilityProbe
- type CockpitInstance
- type Coder
- type GlobalInstructions
- type Manager
- func (s *Manager) Activity(rawID string, entries, budget int) (Activity, error)
- func (s *Manager) AttachStream(rawID, rawCols, rawRows string) (terminal.Attachment, error)
- func (s *Manager) Coder() Coder
- func (s *Manager) DeleteResumable(rawID string) (Session, error)
- func (s *Manager) DetachStream(name string)
- func (s *Manager) ID() string
- func (s *Manager) Invalidate()
- func (s *Manager) OwnsStream(rawID string) bool
- func (s *Manager) RefreshStream(name string, generation int64) (terminal.Attachment, bool)
- func (s *Manager) Resize(rawID, rawCols, rawRows string) error
- func (s *Manager) Resnapshot(name string) (terminal.Attachment, bool)
- func (s *Manager) Resolve(rawID string) error
- func (s *Manager) ResolveResumable(rawID string) (Session, error)
- func (s *Manager) ResolveRunning(rawID string) (Running, error)
- func (s *Manager) Resume(rawID string) (Session, error)
- func (s *Manager) ResumeReserved(rawSessionID, rawWorkdir, title string) (Session, error)
- func (s *Manager) RunSessionWatch(interval time.Duration, onOutput, onBell func(targetID string))
- func (s *Manager) RunTurnWatch(interval time.Duration, onSeen func(id string, startedAt time.Time), ...)
- func (s *Manager) Send(rawID string, items []terminal.Input) error
- func (s *Manager) SetHidden(hidden func(sessionID string) bool)
- func (s *Manager) Snapshot() Snapshot
- func (s *Manager) Start(rawName, rawProject, rawAgent string, opts StartOptions) (StartResult, error)
- func (s *Manager) Stop(rawID string) (string, error)
- func (s *Manager) StopIdleStreams() error
- func (s *Manager) StreamDelta(name string, offset int64) ([]byte, int64, bool)
- func (s *Manager) StreamExited(name string) bool
- func (s *Manager) StreamModes(name string) (tmux.PaneModes, bool)
- func (s *Manager) StreamUpdated(name string) (<-chan struct{}, bool)
- type Registry
- type Running
- type Session
- type SessionNaming
- type SessionRepository
- type SessionRuntime
- type SessionStart
- type Skill
- type SkillRepository
- type SkillSaveResult
- type Snapshot
- type StartOptions
- type StartResult
- type WorkdirTruster
Constants ¶
const ActivityBudget = 4000
ActivityBudget is how many runes a whole activity reading may cost by default. Callers pass it as the budget; zero lifts the cap, for the one answer that needs a message whole.
const CockpitGitSkillID = "dev-cockpit-git"
CockpitGitSkillID names the one skill the cockpit writes itself: the coder side of the git proxy. The id is the marker, the skills UI renders it locked and the handlers refuse to edit or delete it.
Variables ¶
var ErrNotRunning = errors.New("No active coder")
ErrNotRunning marks lookups for identifiers without a live coder session.
Functions ¶
func AssistantRunnerFor ¶
AssistantRunnerFor returns the coder's conversation runner, or nil.
func DisplayName ¶
DisplayName returns name when present, otherwise a stable short fallback.
func EnsureManagedSkills ¶
func EnsureManagedSkills(repo SkillRepository, inst CockpitInstance) error
EnsureManagedSkills writes the cockpit's own skills into one coder's global skill directory and brings an outdated copy up to date. It runs at start and renders from the running configuration, so a new wording, a moved binary or changed start flags reach every coder without anybody doing anything; an unchanged skill writes nothing.
What it writes over is only ever the cockpit's own (managedSkillMark). Somebody's own skill under that name stays untouched, and the refusal says so: this may take a skill of theirs neither over nor away. A copy somebody edited still carries the mark and is rewritten, which is what "kept current" means; one whose mark is gone is not recognisable as the cockpit's any more and is left alone.
A skill of another instance is taken over only when that instance stopped serving. Both cases look the same on the disk, and only one of them may be walked over: a cockpit running right now beside this one keeps the slot, while a state directory nobody answers from is the leftover of a start with other flags, and refusing that one would leave every coder without the skill until somebody cleaned up by hand.
func IsManagedSkill ¶
IsManagedSkill reports whether a skill id belongs to the cockpit. Managed skills are written at start and kept current by the serve process; the settings pages show them with that note and let nobody edit, overwrite or delete them there.
func LessSession ¶
LessSession orders sessions newest-first, with name and ID as tie-breakers.
func NormalizeCWD ¶
NormalizeCWD resolves symlinks so working directories compare reliably.
func ParseTimestamp ¶
ParseTimestamp parses an RFC3339 timestamp as written by coder-CLI state files.
func RemoveManagedSkills ¶
func RemoveManagedSkills(repo SkillRepository, inst CockpitInstance) error
RemoveManagedSkills takes the cockpit's own skills off the disk again. It runs when the cockpit stops, because the skill tells a coder to reach a cockpit that is about to stop answering: the command needs the local API socket of a running instance, so a skill that outlived the process would send every coder down a path that cannot work. The skill is rendered state and no configuration of anybody's, so removing it loses nothing, and the next start writes it again.
A skill that is not there is not an error: a start that could not write it, a stop after a previous one already cleaned up, and a coder installed while the cockpit ran all end here with nothing to do. Neither is a skill that is not this instance's, and that one is the case worth naming: somebody's own skill under this name, or the skill of the instance still running next to this one, which a throwaway's stop would otherwise delete out from under every coder of the real instance.
func SanitizeAgentID ¶
func SanitizeSkillID ¶
Types ¶
type Activity ¶
type Activity struct {
// Text is what the session last did, newest last, bounded to what a reader
// can take in.
Text string
// Finished says the session's turn is over: it is waiting, not working.
Finished bool
// Screen says Text is the terminal picture rather than a recorded
// conversation, so it carries the coder's input line and whatever draft
// stands in it. A reader has to be told that, because a draft is not a
// message.
Screen bool
// InToolCall says the record ends inside a tool call the coder still
// owes a result to. It refines an unfinished turn for the interrupt
// heuristic, see ActivityProfile.InterruptKeys; a reader that cannot
// tell leaves it false.
InToolCall bool
// AwaitingApproval says the record ends on a permission ask nobody has
// answered yet: the turn is not over, but the coder is waiting on its
// person, not working. A reader whose record does not mark the ask
// leaves it false; claude's transcript is such a record, its ask is
// heard as a hook signal instead.
AwaitingApproval bool
// LastMessageAt is when the newest recorded message was written, zero
// when the reader cannot say. The record watcher needs it to tell a
// message of the running terminal's life from one a previous life left
// behind: a coder touches its record with bookkeeping at boot and exit,
// so the file moving proves nothing about the conversation moving.
LastMessageAt time.Time
}
Activity is what a coder says about one of its sessions without attaching to it: what it last did, and whether it is still doing something. Why it stopped is not answered here and cannot be: a coder waiting on a dialog, one with no room left to think in and one that is simply done all look the same from outside, and telling them apart takes reading the screen and understanding it. That is what the check does, with the screen in front of it.
type ActivityProfile ¶ added in v1.59.0
type ActivityProfile struct {
// WatchRecord follows the session's record through RunTurnWatch. A
// coder setting it must implement ActivityReporter and ActivityStamper;
// a coder without a readable record leaves it off and reports its turns
// itself (opencode's plugin events).
WatchRecord bool
// InterruptKeys takes a typed Escape or Ctrl+C as the hint that a turn
// was aborted, for a coder whose aborts may leave no written trace. The
// hint never applies inside a tool call (see Activity.InToolCall): an
// abort there writes its own marker, while the same key may just be
// closing a dialog over the running turn.
InterruptKeys bool
// OpenTurnCap ends an open turn whose record and signals have said
// nothing at all for this long, the backstop against an end that was
// never written anywhere; zero or less disables it. Generous on
// purpose: a quiet tool call is a legitimate long silence.
OpenTurnCap time.Duration
// MovementStartGrace mutes the movement shelf for a session's first
// moments: a freshly started TUI paints its whole boot without anybody
// working, and after a reboot every restored session boots at once. A
// real first turn is visible through the account regardless; zero or
// less mutes nothing.
MovementStartGrace time.Duration
}
ActivityProfile is a coder's choice among the shared working-mark heuristics. The heuristics themselves live once, in internal/activity and the watchers; whether one applies to a coder is decided here and nowhere else, because a rule that saves one coder can break another: an interrupt key means abort to claude and may mean nothing of the sort elsewhere. Every coder answers this through the required Coder.ActivityProfile.
type ActivityReporter ¶
type ActivityReporter interface {
SessionActivity(sessionID string, entries, budget int) (Activity, error)
}
ActivityReporter is the optional capability of a coder to report a session's activity from its own records. A coder implements it when its CLI keeps a record of the session on disk; for a coder that keeps none, the manager falls back to the terminal picture, which is the only source left.
entries is how many recorded messages the reading keeps, newest last; zero or less means the coder's own default. budget is how many runes the whole reading may cost; zero or less means unlimited, and a message the budget cuts has to say in the text how much of it is shown, never end in a bare ellipsis. Fewer entries leave more room per message within the same budget.
type ActivityStamper ¶ added in v1.59.0
ActivityStamper is the optional capability of a coder to say when a session's record last moved, without reading it. It is what lets the record watcher (RunTurnWatch) follow every running session cheaply: a stamp per tick, a real reading only when the stamp moved. A coder whose record cannot be statted, opencode's lives behind its CLI, simply does not implement it and is followed by its own push events instead.
type AgentOption ¶
AgentOption is a UI-friendly choice for the agent dropdown.
type AgentRepository ¶
type AgentRepository interface {
List() []Agent
Options() []AgentOption
Find(rawID string) (Agent, error)
ValidateSelected(rawID string) (string, error)
Save(originalRawID, rawID, rawDesc, rawInstr string) (AgentSaveResult, error)
Delete(rawID string) (Agent, error)
}
AgentRepository manages coder-specific stored agents.
func NewStandardAgentRepository ¶
func NewStandardAgentRepository(dir, fileSuffix string) AgentRepository
type AgentSaveResult ¶
AgentSaveResult conveys what happened when saving an agent.
type AssistantCapable ¶
AssistantCapable is the optional conversation capability. A coder implements it when its CLI can answer a prompt non-interactively and stream the answer; the method returns nil when the installed version cannot. Terminal behavior does not depend on it, a coder without it keeps every other surface.
The dependency points this way on purpose: internal/assistant never imports internal/coder, so the two packages cannot form a cycle.
type CapabilityProbe ¶
type CapabilityProbe struct {
// contains filtered or unexported fields
}
CapabilityProbe caches an expensive check of an installed CLI. A pass is final for the life of the process, the binary does not lose a flag while the server runs. A miss is retried once the pause is over, because a failed probe often describes the moment rather than the CLI, for example an update replacing the binary right when the probe ran. Freezing that first impression would keep the capability off until the next server restart.
func NewCapabilityProbe ¶
func NewCapabilityProbe(check func() bool, pause time.Duration) *CapabilityProbe
NewCapabilityProbe builds a probe around check. pause is how long a miss is trusted before the check may run again.
func (*CapabilityProbe) Passed ¶
func (p *CapabilityProbe) Passed() bool
Passed reports whether the check has succeeded, running it when its last answer is no longer usable. The lock stays held while the check runs, so concurrent callers wait for one probe instead of starting their own.
type CockpitInstance ¶
type CockpitInstance struct {
Executable string
StateDir string
Running func(stateDir string) bool
}
CockpitInstance is the instance a managed skill is rendered for and belongs to: what to run, which cockpit it reaches, and the one question that cannot be answered from a file.
Running answers whether another instance is still serving from a state directory, and it is what tells the two cases apart that look identical on the disk: a second cockpit running right now beside this one, and this same cockpit restarted with a different --state-dir. The first one owns the skill and keeps it, the second left nothing behind but the mark of a state directory nobody serves from any more, and that one is taken over. Nil reads as nobody serving, so a caller that does not ask takes the skill over.
type Coder ¶
type Coder interface {
ID() string
RequiredTools() []string
AgentRepository() AgentRepository
SessionRepository() SessionRepository
SkillRepository() SkillRepository
GlobalInstructions() GlobalInstructions
SessionRuntime() SessionRuntime
ControlMapper() terminal.ControlMapper
// ActivityProfile is the coder's own, deliberate choice of the shared
// working-mark heuristics, see the type. It is a required method on
// purpose: which heuristic may touch which coder is a per-coder
// decision, and a new coder has to make it in the open rather than
// inherit whatever happened to be wired.
ActivityProfile() ActivityProfile
}
Coder is a dependency bag for coder-specific collaborators.
type GlobalInstructions ¶
GlobalInstructions reads and writes coder-wide instructions.
func NewFileGlobalInstructions ¶
func NewFileGlobalInstructions(filePath string) GlobalInstructions
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager orchestrates the session lifecycle.
func NewManager ¶
func NewManager( cfg config.Config, t *tmux.Client, p Coder, projects *project.Repository, ) *Manager
NewManager wires up a coder Manager with its dependencies.
func (*Manager) Activity ¶
Activity says what a session last did and whether its turn is over. The coder answers when it can, because its own record is reliable; only for a coder without one does this fall back to the picture on the screen. entries and budget are passed through to the reporter, see ActivityReporter; the screen fallback ignores them, it is bounded by lines instead.
func (*Manager) AttachStream ¶
func (s *Manager) AttachStream(rawID, rawCols, rawRows string) (terminal.Attachment, error)
AttachStream opens the control client and returns the initial snapshot.
func (*Manager) DeleteResumable ¶
DeleteResumable removes the stored session directory.
func (*Manager) DetachStream ¶
DetachStream releases one browser stream and closes the control client after the last one.
func (*Manager) OwnsStream ¶
OwnsStream reports whether this service has a live browser stream for the identifier, letting callers route input without a process-table scan.
func (*Manager) RefreshStream ¶
RefreshStream returns a new snapshot when another browser reset this stream.
func (*Manager) Resnapshot ¶
func (s *Manager) Resnapshot(name string) (terminal.Attachment, bool)
Resnapshot recaptures the screen for a stream that fell out of the ring.
func (*Manager) Resolve ¶
Resolve reports whether a coder session with the given identifier is live.
func (*Manager) ResolveResumable ¶
ResolveResumable looks up a stored session by id.
func (*Manager) ResolveRunning ¶
ResolveRunning validates the identifier and returns the matching Running entry.
func (*Manager) ResumeReserved ¶
ResumeReserved brings a session back that the visibility filter hides, the one deliberate way past it. A chat drives a real provider session and keeps it hidden from every coder surface; when the chat is handed over, this starts the terminal on that exact conversation instead of a fresh one.
It goes through the same body as Resume, so pane tagging, environment and snapshot invalidation cannot drift apart. It cleans up after itself: a failure between the tmux start and the tagging kills the half-built session, otherwise the caller would be left with a pane it does not know about.
func (*Manager) RunSessionWatch ¶ added in v1.59.0
RunSessionWatch watches every running coder of this manager through a read-only control client and reports two raw facts, classifying neither: onOutput hears every output chunk, which is what feeds the working mark, and onBell hears terminal bells under the usual cooldown. Copilot's beep option rings BEL when a turn finishes and when a dialog waits for input; either way the coder has news, so bells are reported as-is without reading the pane. Either callback may be nil. Blocks; run it in a goroutine.
func (*Manager) RunTurnWatch ¶ added in v1.59.0
func (s *Manager) RunTurnWatch(interval time.Duration, onSeen func(id string, startedAt time.Time), onTurn func(id string, open, inTool bool, at time.Time), onGone func(id string), onRenamed func(id, name, cwd string))
RunTurnWatch follows every running session of this manager for the activity tracker. onSeen(id) fires once when a session enters the running set, which is where the tracker learns the coder's chosen policy; onGone(id) when it leaves, so a mark cannot outlive its terminal. For a coder whose ActivityProfile chose WatchRecord, onTurn reports the record's own account whenever it moved: whether a turn is open, whether it stands inside a tool call, and the record's stamp as the word's own time.
onRenamed reports a session whose display name moved since the last tick. A coder names its own sessions, so a rename happens inside the CLI and reaches no handler that could announce it; the name is in the snapshot this loop reads anyway, so it costs one string compare. It is compared above the record section on purpose: a coder without WatchRecord leaves there, and its sessions get renamed like everybody else's.
A stamp is taken per session per tick and the record is only read when the stamp moved, so an idle session costs one stat per tick. A session whose record cannot be stamped or read yet reports nothing at all: no account is exactly what the movement fallback is for. Blocks; run it in a goroutine.
func (*Manager) Send ¶
Send dispatches a batch of user inputs to a session, in order. It resolves the target once and stops at the first failing item. A prompt into a freshly started session waits at the gate first, see gatePrompt; keys and raw input stay immediate, a dialog answer must not lag behind the dialog.
func (*Manager) SetHidden ¶
SetHidden installs the session visibility filter. It must be set before the first snapshot, and the predicate must never call back into this manager.
func (*Manager) Snapshot ¶
Snapshot returns the cached view of running/resumable sessions, recomputing it after the TTL or an Invalidate.
func (*Manager) Start ¶
func (s *Manager) Start(rawName, rawProject, rawAgent string, opts StartOptions) (StartResult, error)
Start creates a new coder session.
func (*Manager) StopIdleStreams ¶
StopIdleStreams clears inherited tmux pipes left by a previous process.
func (*Manager) StreamDelta ¶
StreamDelta returns buffered output after offset. reset is true when the caller fell out of the ring and must re-snapshot.
func (*Manager) StreamExited ¶
StreamExited reports whether the underlying control client has ended.
func (*Manager) StreamModes ¶
StreamModes reads the pane's current terminal modes, so a stream can tell the browser when a program switched into or out of its full screen UI.
func (*Manager) StreamUpdated ¶
StreamUpdated returns a channel closed on the next output or exit, plus whether the stream is still live.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
func NewRegistry ¶
type Running ¶
type Running struct {
Identifier string // stable session ID used in URLs/UI
TmuxSession string // underlying tmux session name
PID string
Name string
StartedAt time.Time
CWD string
TabPos int // tab strip position from @dc_tab_pos, 0 when unset
TabGroup string // split view group id from @dc_tab_group, empty when ungrouped
TabGroupPos int // position inside the group from @dc_tab_gpos, 0 when unset
TabGroupName string // group display name from @dc_tab_gname, may be empty
TabGroupCol int // column inside the group from @dc_tab_gcol, 0 for a column of its own
}
Running is one live tmux session backed by a recognised coder process.
type SessionNaming ¶ added in v1.56.0
type SessionNaming interface {
NamesSessions() bool
}
SessionNaming is the optional answer to whether the CLI carries the cockpit's chosen name into the session record it creates. A runtime that does not implement it is taken to name its sessions, which is what every coder before opencode did: copilot has --name, claude takes the whole id. A runtime that answers false has a CLI with no name flag and no id flag, so the promote step can never find the fresh session by its name and matches it on the working directory alone.
type SessionRepository ¶
type SessionRepository interface {
List() []Session
DeleteSession(sessionID string) error
ListFiles(sessionID string) ([]filesystem.File, error)
SaveFile(sessionID, rawName string, src io.Reader) (filesystem.File, error)
OpenFile(sessionID, rawName string) (filesystem.OpenedFile, error)
DeleteFile(sessionID, rawName string) (filesystem.File, error)
}
SessionRepository manages coder-specific persisted sessions and files.
type SessionRuntime ¶
type SessionRuntime interface {
UsesProvidedSessionID() bool
StartCommand(start SessionStart) string
ResumeCommand(sessionID, workdir string, automaticApproval bool) string
Env() map[string]string
}
SessionRuntime builds coder-specific start and resume commands.
type SessionStart ¶
type SessionStart struct {
SessionID string
Name string
Workdir string
AgentID string
AutomaticApproval bool
Task string
}
SessionStart is what a new coder session needs to come up. Task is optional: when it is set the CLI is started with that first prompt in its argv, which is the only delivery that cannot be lost. Typing into a pane that has not read stdin yet loses the text without a trace.
type SkillRepository ¶
type SkillRepository interface {
List() []Skill
Find(rawID string) (Skill, error)
Save(originalRawID, rawID, rawDesc, rawInstr string) (SkillSaveResult, error)
Delete(rawID string) (Skill, error)
}
SkillRepository manages coder-specific global skills.
func NewStandardSkillRepository ¶
func NewStandardSkillRepository(dir string) SkillRepository
type SkillSaveResult ¶
SkillSaveResult conveys what happened when saving a skill.
type Snapshot ¶
type Snapshot struct {
Running []Running
Inactive []Session
Resumable []Session // every stored session, including those already running
}
Snapshot captures the present state of every session category.
type StartOptions ¶
type StartOptions struct {
AutomaticApproval bool
// Task is the first prompt the session comes up with. It travels in the
// CLI's argv, never typed into the pane, so it cannot be lost to a CLI that
// has not read stdin yet.
Task string
}
StartOptions control how a new provider session is launched.
type StartResult ¶
StartResult is returned by Start.
type WorkdirTruster ¶
WorkdirTruster is the optional capability of a coder runtime to record a working directory as trusted in the CLI's own configuration, the way the CLI records it after its trust dialog was answered.
It exists because a coder that comes up in a directory its CLI has never seen asks "do you trust the files in this folder?" before it reads anything else, and the task travels in the argv: the session sits on a dialog with the work behind it while the caller was told the coder is working. There is no flag for it on either CLI, the answer is state in the CLI's own config, so this writes what the dialog would have written. A runtime whose CLI has no such state leaves the method out.