coder

package
v1.62.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Overview

DisplayName and ShortID format fallback labels for coder sessions.

Index

Constants

View Source
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.

View Source
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

View Source
var ErrNotRunning = errors.New("No active coder")

ErrNotRunning marks lookups for identifiers without a live coder session.

Functions

func AssistantRunnerFor

func AssistantRunnerFor(c Coder) assistant.Runner

AssistantRunnerFor returns the coder's conversation runner, or nil.

func DisplayName

func DisplayName(name, sessionID string) string

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

func IsManagedSkill(id string) bool

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

func LessSession(a, b Session) bool

LessSession orders sessions newest-first, with name and ID as tie-breakers.

func NormalizeCWD

func NormalizeCWD(path string) string

NormalizeCWD resolves symlinks so working directories compare reliably.

func ParseTimestamp

func ParseTimestamp(raw string) (time.Time, bool)

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 SanitizeAgentID(raw string) (string, error)

func SanitizeSkillID

func SanitizeSkillID(raw string) (string, error)

func ShortID

func ShortID(id string) string

ShortID returns a compact form of an identifier for display.

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

type ActivityStamper interface {
	SessionActivityStamp(sessionID string) (time.Time, error)
}

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 Agent

type Agent struct {
	ID           string
	Description  string
	Instructions string
	Path         string
}

Agent is a stored coder-specific agent definition.

type AgentOption

type AgentOption struct {
	Value string
	Label string
}

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

type AgentSaveResult struct {
	Saved   Agent
	Created bool
}

AgentSaveResult conveys what happened when saving an agent.

type AssistantCapable

type AssistantCapable interface {
	AssistantRunner() assistant.Runner
}

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

type GlobalInstructions interface {
	Read() (string, error)
	Save(raw string) error
}

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

func (s *Manager) Activity(rawID string, entries, budget int) (Activity, error)

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) Coder

func (s *Manager) Coder() Coder

Coder returns the coder definition this manager serves.

func (*Manager) DeleteResumable

func (s *Manager) DeleteResumable(rawID string) (Session, error)

DeleteResumable removes the stored session directory.

func (*Manager) DetachStream

func (s *Manager) DetachStream(name string)

DetachStream releases one browser stream and closes the control client after the last one.

func (*Manager) ID

func (s *Manager) ID() string

ID returns the coder id.

func (*Manager) Invalidate

func (s *Manager) Invalidate()

Invalidate flushes the snapshot cache.

func (*Manager) OwnsStream

func (s *Manager) OwnsStream(rawID string) bool

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

func (s *Manager) RefreshStream(name string, generation int64) (terminal.Attachment, bool)

RefreshStream returns a new snapshot when another browser reset this stream.

func (*Manager) Resize

func (s *Manager) Resize(rawID, rawCols, rawRows string) error

Resize sets the tmux window size.

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

func (s *Manager) Resolve(rawID string) error

Resolve reports whether a coder session with the given identifier is live.

func (*Manager) ResolveResumable

func (s *Manager) ResolveResumable(rawID string) (Session, error)

ResolveResumable looks up a stored session by id.

func (*Manager) ResolveRunning

func (s *Manager) ResolveRunning(rawID string) (Running, error)

ResolveRunning validates the identifier and returns the matching Running entry.

func (*Manager) Resume

func (s *Manager) Resume(rawID string) (Session, error)

Resume brings a stored session back to life.

func (*Manager) ResumeReserved

func (s *Manager) ResumeReserved(rawSessionID, rawWorkdir, title string) (Session, error)

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

func (s *Manager) RunSessionWatch(interval time.Duration, onOutput, onBell func(targetID string))

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

func (s *Manager) Send(rawID string, items []terminal.Input) error

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

func (s *Manager) SetHidden(hidden func(sessionID string) bool)

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

func (s *Manager) Snapshot() 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) Stop

func (s *Manager) Stop(rawID string) (string, error)

Stop kills the running tmux session and closes its control client.

func (*Manager) StopIdleStreams

func (s *Manager) StopIdleStreams() error

StopIdleStreams clears inherited tmux pipes left by a previous process.

func (*Manager) StreamDelta

func (s *Manager) StreamDelta(name string, offset int64) ([]byte, int64, bool)

StreamDelta returns buffered output after offset. reset is true when the caller fell out of the ring and must re-snapshot.

func (*Manager) StreamExited

func (s *Manager) StreamExited(name string) bool

StreamExited reports whether the underlying control client has ended.

func (*Manager) StreamModes

func (s *Manager) StreamModes(name string) (tmux.PaneModes, bool)

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

func (s *Manager) StreamUpdated(name string) (<-chan struct{}, bool)

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

func NewRegistry(ps ...Coder) *Registry

func (*Registry) All

func (r *Registry) All() []Coder

All returns the coders in registration order, which doubles as the default preference order when several coders are active.

func (*Registry) ByID

func (r *Registry) ByID(rawID string) Coder

func (*Registry) IDs

func (r *Registry) IDs() []string

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 Session

type Session struct {
	SessionID string
	Name      string
	CWD       string
	UpdatedAt time.Time
}

Session is one stored coder-CLI session that can be resumed.

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 Skill

type Skill struct {
	ID           string
	Description  string
	Instructions string
	Path         string
}

Skill is a stored coder-specific global skill definition.

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

type SkillSaveResult struct {
	Saved   Skill
	Created bool
}

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

type StartResult struct {
	Identifier string
	Name       string
	Workdir    string
	AgentID    string
}

StartResult is returned by Start.

type WorkdirTruster

type WorkdirTruster interface {
	TrustWorkdir(workdir string) error
}

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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL