coder

package
v1.54.1 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 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
}

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 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 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
}

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

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

RunBellWatch watches every running coder of this manager and reports terminal bells. 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 classifying the pane. 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 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