agent

package
v0.3.3 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package agent defines the interface and types for coding agent backends.

Each backend (OpenCode, Claude Code) implements the Backend interface, allowing the daemon to manage multiple agent types uniformly.

Index

Constants

View Source
const (
	// ConfigOptionMode is the config-option id every serving agent
	// advertises for its session mode. Routed through ACP
	// session/set_mode; every other option id rides
	// session/set_config_option.
	ConfigOptionMode = "mode"
	// ConfigOptionModel is the conventional id/category of the model
	// config option, the semantic key model pickers branch on.
	ConfigOptionModel = "model"
)
View Source
const (
	// AuthTypeDevice — RFC 8628 device-code OAuth. Backend polls the
	// provider's token endpoint; user enters user_code in a browser.
	// Used by github-copilot.
	AuthTypeDevice = "device"
	// AuthTypeAPI — paste-a-string. Optional Prompts collect extra
	// metadata (Azure resource name, Cloudflare account ID, etc.).
	AuthTypeAPI = "api"
	// AuthTypeOAuthCode — two-step browser auth with user-pasted code.
	// Backend spawns the IdP's CLI (claude setup-token) in a PTY,
	// returns the authorize URL it prints, accepts the code the user
	// copies from the IdP's hosted callback page, writes it to the
	// CLI's stdin, and captures the long-lived token from stdout.
	// Used by Anthropic (Claude subscription) — works on a remote
	// sprite since the IdP renders the code on its own page rather
	// than redirecting to a localhost callback.
	AuthTypeOAuthCode = "oauth-code"
)

AuthType discriminator values. Clients dispatch the begin-flow based on which one a provider declares.

View Source
const (
	// CredentialSourceStore is a credential clank stored itself (the
	// anthropic sink or opencode's auth.json) — connected and
	// disconnectable through clank.
	CredentialSourceStore = "store"
	// CredentialSourceClaudeCLI means no stored credential, but the
	// machine's own claude CLI login exists and the spawned claude
	// will use it (laptop hosts only). clank stores nothing and cannot
	// disconnect it — that's `claude /logout`'s job — so clients
	// should hide the disconnect affordance for this source.
	CredentialSourceClaudeCLI = "claude_cli"
	// CredentialSourceEnv means no stored credential, but the
	// provider's env var is set in the host process's environment and
	// spawned agent CLIs inherit it (operator-injected on sandboxes,
	// shell exports on laptops). Not disconnectable through clank —
	// clients should hide the disconnect affordance for this source.
	CredentialSourceEnv = "env"
	// CredentialSourceCodexCLI means clank didn't run the codex login
	// ceremony, but the machine's own codex CLI login ($CODEX_HOME/
	// auth.json) exists and the codex adapter will use it (laptop
	// hosts only). Disconnecting through clank would log the user's
	// own CLI out, so clients should hide the disconnect affordance
	// for this source — that's `codex logout`'s job.
	CredentialSourceCodexCLI = "codex_cli"
)

CredentialSource values for ProviderAuthInfo.Source.

View Source
const ClaudeToolExitPlanMode = "ExitPlanMode"

ClaudeToolExitPlanMode is the Claude tool name carried on ExitPlanMode permission prompts (via the adapter's _meta.claudeCode.toolName). Clients render those prompts as a plan review (approve/deny).

View Source
const DefaultBackend = BackendOpenCode

DefaultBackend is the backend used when neither a CLI flag nor a user preference specifies one. Centralised so we don't hard-code "opencode" at every entry point.

View Source
const EnvWorktreeID = "CLANK_WORKTREE_ID"

EnvWorktreeID overrides the cached ID resolution. Intended for CI and tests that want to pin an ID without touching the repo's .git.

View Source
const PinnedBunVersion = "1.3.14"

PinnedBunVersion is the bun version the host image installs and uses to install the pinned agent CLIs. bun is infra (installer + JS runtime for the CLIs), pinned here so image builds read it from source alongside the CLI pins — see `clank-host print-pins`.

View Source
const PinnedClaudeVersion = "2.1.217"

PinnedClaudeVersion is the Claude Code CLI version clank ships against. Bumping this constant is a deliberate, reviewable change — it determines what every fly.io provisioner installs onto a sprite.

Why pin: the sprite base image bakes its own claude CLI with auto-updates disabled, frozen at whatever was current when the image was built. The CLI is not just a runtime: the family aliases clank passes as models (sonnet / opus / haiku / fable — see internal/host/backends.go) resolve to a CONCRETE model inside the CLI binary, so a stale claude silently downgrades every session's model. Seen 2026-07-05: an image-baked 2.1.168 resolved `sonnet` to Sonnet 4.6 well after newer families shipped, and that CLI vintage also retry-looped truncated thinking-only turns, burning 32k output tokens per attempt without ever calling a tool.

The pinned value must be a published @anthropic-ai/claude-code npm version — the sprite-side installer feeds it to `bun install -g @anthropic-ai/claude-code@<pin>` and hard-fails on a version mismatch.

Bumping this:

  1. Update the constant.
  2. `make install` — laptops get the new clank that knows the new pin.
  3. Sprites probe-and-reinstall on next EnsureHost.

Since the ACP migration this pin governs the standalone claude CLI used for AUTH ONLY (`claude setup-token`, borrowed-login probe) — the agent runtime is the claude-agent-acp adapter's own bundled CLI, pinned by acptools' lockfile. Keep this tracking the adapter's bundled vintage so both paths agree on credential formats.

Unlike PinnedOpencodeVersion there is no laptop-side compat gate: claude session blobs never round-trip through clank migrations, so drift is a quality problem, not a corruption problem.

View Source
const PinnedOpencodeVersion = "1.17.18"

PinnedOpencodeVersion is the opencode version clank ships against on provisioned hosts, and the verified-surface floor for `opencode acp` on laptops (the ACP manager refuses older binaries with an upgrade hint). Bumping this constant is a deliberate, reviewable change — it determines what every fly.io provisioner installs onto a host (and what `clank-host print-pins` reports).

Bumping this:

  1. Update the constant (and re-verify the `opencode acp` surface).
  2. `make install` — laptops get the new clank that knows the new pin.
  3. Sprites probe-and-reinstall on next EnsureHost (~30-90s one-shot cost).
  4. Laptops below the floor see the upgrade hint at first opencode use.

Variables

AllBackends lists every backend the daemon knows how to launch, in a stable display order. Used by the settings UI to cycle / pick.

View Source
var AllowLocalFileAttachments bool

AllowLocalFileAttachments gates file:// attachment sources. clank-host enables it only in laptop (socket) mode, where the client shares its filesystem; a remote sprite leaves it false so a message can't make it read arbitrary local paths.

ClaudePermissionModes is the ordered set the TUI cycles through (Tab).

View Source
var ErrUnsupported = errors.New("operation not supported by this backend")

ErrUnsupported marks an operation a backend does not implement (e.g. fork on a backend without fork support). The HTTP layer maps it to 501 with code "unsupported" so clients can degrade gracefully instead of treating it as an internal error.

Functions

func ClaudeVersion

func ClaudeVersion(ctx context.Context) (string, error)

ClaudeVersion runs `claude --version` and returns the parsed bare version (e.g. "2.1.201"). The binary is resolved via PATH — the same lookup the claude-agent-sdk uses to spawn session CLIs — so the reported version is the one sessions will actually run.

The subprocess is called with no special env so it inherits clank-host's environment (HOME etc.). Reading the version doesn't touch session storage, so isolation isn't required.

func DataURL

func DataURL(mime string, data []byte) string

DataURL renders bytes as an RFC 2397 data: URL — used to inline an image into an OpenCode file part and to build an inline attachment Source.

func GitDir

func GitDir(projectDir string) (string, error)

GitDir resolves the per-worktree git directory for projectDir. For the main worktree this is <repo>/.git; for a linked worktree created by `git worktree add` it's <repo>/.git/worktrees/<name>/. Returns an error if projectDir isn't inside a git repo (or git is missing from PATH).

func IsAnyProviderConnected

func IsAnyProviderConnected(providers []ProviderAuthInfo) bool

IsAnyProviderConnected reports whether at least one provider in the snapshot has a usable credential — stored by clank or borrowed from the machine's own CLI login / environment.

func IsBackendConnected

func IsBackendConnected(providers []ProviderAuthInfo, backend BackendType) bool

IsBackendConnected reports whether backend has at least one connected provider in the snapshot. A snapshot filtered to another backend always answers false — it carries no evidence either way.

func OpenCodeVersion

func OpenCodeVersion(ctx context.Context) (string, error)

OpenCodeVersion runs `opencode --version` and returns the trimmed stdout. opencode 1.x prints just the bare version (e.g. "1.14.48"). Callers gate on it via OpencodeVersionAtLeast (the ACP floor) or report it in the software manifest.

The subprocess inherits clank-host's environment (HOME etc.); reading the version doesn't touch session storage.

func OpencodeVersionAtLeast

func OpencodeVersionAtLeast(v, floor string) (bool, error)

OpencodeVersionAtLeast reports whether version v is >= floor. Used by the ACP path to gate `opencode acp` on a verified-surface floor. Returns an error when either version fails to parse.

func ParseClaudeVersionOutput

func ParseClaudeVersionOutput(out string) string

ParseClaudeVersionOutput extracts the bare version from `claude --version` output. The CLI prints a suffixed form ("2.1.201 (Claude Code)"), so exact-matching raw output against PinnedClaudeVersion would never succeed; callers compare the first whitespace-delimited field instead. Returns "" for empty or all-whitespace input.

func ParseTimeParam

func ParseTimeParam(s string) (time.Time, error)

ParseTimeParam parses a time string that is either a relative duration suffix (e.g. "7d", "24h") interpreted as "ago from now", or an RFC 3339 timestamp. Supported relative units: h (hours), d (days).

func ReadLocalWorktreeID

func ReadLocalWorktreeID(projectDir string) (string, error)

ReadLocalWorktreeID returns the worktree ULID cached for projectDir:

  1. $CLANK_WORKTREE_ID if non-empty.
  2. <gitDir>/clank/worktree-id (where gitDir = git rev-parse --absolute-git-dir).
  3. "" if the file is missing or projectDir is not inside a git repo.

Errors other than "not a git repo" / "file missing" propagate so a misconfiguration (bad permissions, etc.) doesn't silently degrade to "no id cached".

func RemoveLocalWorktreeID

func RemoveLocalWorktreeID(projectDir string) (bool, error)

RemoveLocalWorktreeID deletes the stamped worktree ULID for projectDir. Returns true if an id was present.

func RepoDisplayName

func RepoDisplayName(g GitRef) string

RepoDisplayName returns a short human-readable label for UIs and logs.

Precedence: explicit DisplayName → basename(LocalPath) → "" for remote-only refs whose owner did not stamp a DisplayName.

func RepoKey

func RepoKey(g GitRef) string

RepoKey returns a stable map key for a GitRef. Used by in-memory dedup tables (e.g. the primary-agents background-refresh set) where the identity is (project, branch).

Prefers WorktreeID because it is the cross-machine stable identity: two clients on different hosts referring to the same project share a WorktreeID but have different LocalPaths. Falls back to LocalPath for refs with no WorktreeID. Returns "" for invalid refs.

Subdir is deliberately excluded: sessions in different subdirectories of one repo share the repo's identity (agent catalogs and sidebar grouping are repo-level).

func WriteLocalWorktreeID

func WriteLocalWorktreeID(projectDir, id string) error

WriteLocalWorktreeID persists the worktree ULID for projectDir at <gitDir>/clank/worktree-id. Idempotent. Errors if projectDir is not inside a git repo (callers stamp freshly created worktrees, which are always real git working trees).

Types

type AgentInfo

type AgentInfo struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Mode        string `json:"mode"`   // "primary", "subagent", or "all"
	Hidden      bool   `json:"hidden"` // Internal agents (compaction, title, summary)
}

AgentInfo is a lightweight summary of an OpenCode agent, used by the TUI to display and cycle through available agents. We define our own struct rather than using the SDK's Agent type because the SDK is missing the "hidden" field that OpenCode returns in GET /agent.

type AgentLister

type AgentLister interface {
	ListAgents(ctx context.Context, projectDir string) ([]AgentInfo, error)
}

AgentLister is an optional interface that BackendManagers can implement to expose available agents for a project.

type AllSessionDiscoverer

type AllSessionDiscoverer interface {
	DiscoverAllSessions(ctx context.Context) ([]SessionSnapshot, error)
}

AllSessionDiscoverer is an optional interface for BackendManagers whose underlying storage allows enumerating every historical session globally (across all known projects) without first naming a seed directory.

Used by the hub's startup-discover pass to heal mis-tagged info.Backend rows: after a corrupted persistence (Backend=opencode for what is really a Claude session), the hub does not know which project dir to query, so the per-seedDir DiscoverSessions path can't find it. AllSessionDiscoverer lets the hub enumerate every snapshot the backend knows about, regardless of the persisted (and potentially wrong) GitRef.LocalPath.

Backends whose discovery model is per-project (e.g. opencode, which boots one HTTP server per project worktree) deliberately do NOT implement this.

type Attachment

type Attachment struct {
	ImageID  string `json:"image_id,omitempty"`
	Mime     string `json:"mime"`
	Filename string `json:"filename,omitempty"`
	// Source is where clank-host fetches the bytes from. Scheme-tagged so the
	// client picks the cheapest transport for the target host:
	//   - file://<abs>      local host, zero-copy (gated by AllowLocalFileAttachments)
	//   - data:<mime>;base64 inline bytes (rides the message)
	//   - http(s)://…       a fetchable URL, e.g. a presigned object-store GET
	Source string `json:"source"`
}

Attachment is one image attached to a user message. The client uploads the bytes to the object store via a presigned PUT URL, then sends the message carrying the presigned GetURL the sprite uses to download. The gateway never sees the bytes.

type AuthCredential

type AuthCredential struct {
	Type    string `json:"type"`
	Refresh string `json:"refresh,omitempty"`
	Access  string `json:"access,omitempty"`
	// Expires must always be serialized even when zero. OpenCode's
	// OAuth schema (packages/opencode/src/auth/index.ts) requires the
	// field; an entry without it is silently dropped at load time —
	// the credential never reaches the provider plugin and the
	// provider stops appearing in /config/providers. Copilot tokens
	// don't have a tracked TTL so 0 is the upstream-blessed value,
	// but it must be present.
	Expires int64  `json:"expires"`
	Key     string `json:"key,omitempty"`

	// EnterpriseURL carries through extra fields the github-copilot
	// plugin populates when the deployment type is "enterprise". The
	// loader uses it to compute the API base URL.
	EnterpriseURL string `json:"enterpriseUrl,omitempty"`

	// Metadata holds provider-specific extra fields for api-type
	// credentials (Azure resourceName, Cloudflare accountId/gatewayId).
	// Empty for providers that need only a key.
	Metadata map[string]string `json:"metadata,omitempty"`
}

AuthCredential is the on-disk credential shape OpenCode reads from `~/.local/share/opencode/auth.json`. Three discriminated variants keyed on Type ("oauth" | "api" | "wellknown"); only the fields for that variant are populated. Mirrors `Oauth` / `Api` / `WellKnown` in packages/opencode/src/auth/index.ts upstream.

For github-copilot the upstream plugin writes `type: "oauth"` with both Refresh and Access set to the same GitHub access_token and Expires=0 (Copilot tokens do not have a tracked TTL in OpenCode). See packages/opencode/src/plugin/github-copilot/copilot.ts.

For api-typed providers that need extra context beyond a single key (Azure resource name, Cloudflare account/gateway IDs, etc.), Metadata carries arbitrary string key-value pairs. The OpenCode provider loader reads these via `auth.metadata?.fieldName` — see the cloudflare/azure plugins for the exact field names.

type BackendInvocation

type BackendInvocation struct {
	// WorkDir is the resolved filesystem path where the backend should run
	// (a repo root, or a worktree path when WorktreeBranch was set).
	WorkDir string

	// ResumeExternalID is the backend's own session ID for resume; empty
	// means start a new backend session. Currently this is sourced from
	// StartRequest.SessionID (which doubles as the host-side session ID).
	// A future split may decouple the two.
	ResumeExternalID string

	// Config is the session's last-applied config (option id → value id).
	// A resumed backend re-asserts it after loading the session: agents
	// boot fresh processes with their own defaults, so without this every
	// rehydrate silently reset mode/model/effort to those defaults. Not an
	// initial config for fresh sessions — those apply config via the first
	// Send (DATA-040).
	Config map[string]string
}

BackendInvocation is the host-resolved, backend-only view of a session start. It is constructed inside host.Service.CreateSession after the (GitRef, WorktreeBranch) → workDir resolution; it never appears on the wire. See §7.4 of hub_host_refactor_code_review.md.

type BackendManager

type BackendManager interface {
	// Init performs eager initialization such as starting servers for known
	// project directories. Called once by the daemon on startup before any
	// other method. Long-running work (like reconciler loops) should be
	// launched as goroutines that respect ctx cancellation.
	// knownDirs returns project directories previously seen for this backend.
	Init(ctx context.Context, knownDirs func() ([]string, error)) error

	// CreateBackend creates a new SessionBackend from a host-resolved
	// invocation. The wire StartRequest is path-free; the Host resolves
	// (RepoRef, Branch) → workDir and constructs a BackendInvocation
	// before invoking this method. See §7.4 of hub_host_refactor_code_review.md.
	// The backend is not started — call Start() or Watch() on it.
	CreateBackend(ctx context.Context, inv BackendInvocation) (SessionBackend, error)

	// Shutdown cleans up all managed resources (servers, connections, etc.).
	Shutdown()
}

BackendManager creates and manages SessionBackend instances for a specific backend type. Each implementation handles its own resource sharing (e.g., OpenCode shares one server per project directory, Claude manages subprocesses independently).

type BackendType

type BackendType string

BackendType identifies which coding agent backend is being used.

const (
	BackendOpenCode   BackendType = "opencode"
	BackendClaudeCode BackendType = "claude-code"
	// BackendCodex is served by the ACP adapter path. Declared ahead of its
	// manager landing; it joins AllBackends (and thus StartRequest.Validate)
	// only once a manager is registered for it.
	BackendCodex BackendType = "codex"
)

func ParseBackend

func ParseBackend(s string) (BackendType, error)

ParseBackend resolves a user-facing backend name (CLI flag, settings file) to a BackendType. The shorthand "claude" is accepted as an alias for "claude-code" for ergonomic CLI usage.

An empty string is rejected — callers should decide whether "" means "use the default" (see ResolveBackendPreference) or is a hard error.

func ParseBackendSet

func ParseBackendSet(csv string) ([]BackendType, error)

ParseBackendSet parses a comma-separated backend list (the --acp-backends flag format). "none" or an empty string yields an empty set; "all" expands to AllBackends. Individual names go through ParseBackend, so aliases work. Duplicates collapse; unknown names error.

func ResolveBackendPreference

func ResolveBackendPreference(s string) (BackendType, error)

ResolveBackendPreference turns the raw string from preferences.json into a BackendType, falling back to DefaultBackend on empty input. An invalid value is treated as "fall back to default" rather than a hard error so a corrupt prefs file never bricks the TUI/CLI — callers get back a non-nil err they can surface as a warning.

type ClaudePermissionMode

type ClaudePermissionMode string

ClaudePermissionMode is the permission posture the Claude Code CLI runs under. Values are the wire strings claude-agent-sdk-go accepts via --permission-mode; they must stay in sync with claudecode.PermissionMode*.

const (
	ClaudePermDefault     ClaudePermissionMode = "default"           // Prompt for each tool that needs permission.
	ClaudePermAcceptEdits ClaudePermissionMode = "acceptEdits"       // Auto-accept file edits; prompt for the rest.
	ClaudePermPlan        ClaudePermissionMode = "plan"              // Plan only; no edits.
	ClaudePermBypass      ClaudePermissionMode = "bypassPermissions" // Skip all permission checks.
	ClaudePermAuto        ClaudePermissionMode = "auto"              // Claude decides routine permissions itself.
)

func (ClaudePermissionMode) IsValid

func (m ClaudePermissionMode) IsValid() bool

IsValid reports whether m is one of the known modes. The empty string is not valid — callers use "" to mean "no mode specified".

func (ClaudePermissionMode) Label

func (m ClaudePermissionMode) Label() string

Label is the short human-readable form shown in the TUI mode indicator.

type ConfigOption

type ConfigOption struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Category    string `json:"category,omitempty"`
	// CurrentValue is the option's live value id — for an unset knob this
	// is the agent's own resolved default (e.g. opencode's concrete
	// default model), which is how clients display truth without clank
	// substituting anything.
	CurrentValue string              `json:"current_value"`
	Values       []ConfigOptionValue `json:"values"`
}

SessionMode is one agent-advertised session mode (the ACP session/set_mode vocabulary). The agent owns the list; clients render it as-is and send back the chosen ID as PermissionMode. ConfigOption is one agent-advertised session config option (ACP SessionConfigOption), served verbatim: the id/value vocabulary is the agent's own. This is the data behind every knob editor — mode, model, effort/reasoning_effort, collaboration_mode, fast — one shape for all of them, so a new option an adapter grows appears in clients with zero clank changes.

type ConfigOptionValue

type ConfigOptionValue struct {
	Value       string `json:"value"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	// Group is the advertised group header, when the agent groups values
	// (e.g. models by provider). Flattened: clients regroup if they care.
	Group string `json:"group,omitempty"`
}

ConfigOptionValue is one selectable value of a ConfigOption.

type ConfigOptionsLister

type ConfigOptionsLister interface {
	ConfigOptions(ctx context.Context, projectDir string) ([]ConfigOption, error)
}

ConfigOptionsLister is an optional interface BackendManagers implement to probe the agent's advertised config options for a project dir before any session exists (the pre-session knob editor's data source).

type ConfigOptionsReporter

type ConfigOptionsReporter interface {
	ConfigOptions() []ConfigOption
}

ConfigOptionsReporter is implemented by backends whose agent advertises session config options; the host stamps them onto runtime SessionInfo and serves them via GET /config-options.

type DeviceFlowStart

type DeviceFlowStart struct {
	FlowID          string    `json:"flow_id"`
	DeviceCode      string    `json:"-"` // not exposed to clients; sandbox-internal
	UserCode        string    `json:"user_code"`
	VerificationURL string    `json:"verification_url"`
	ExpiresAt       time.Time `json:"expires_at"`
	Interval        int       `json:"interval"`
}

DeviceFlowStart is the response body for POST /auth/{provider}/device/start. FlowID identifies the in-memory flow on subsequent status polls and cancellation. UserCode is what the user types into VerificationURL in their browser.

type DeviceFlowState

type DeviceFlowState string

DeviceFlowState enumerates the states of a device-flow lifecycle. pending → authorized → success is the happy path; the auth.json write happens at the pending→authorized boundary, the OpenCode server restart happens during authorized, and the transition to success only fires once the new server is healthy.

const (
	DeviceFlowPending    DeviceFlowState = "pending"
	DeviceFlowAuthorized DeviceFlowState = "authorized"
	DeviceFlowSuccess    DeviceFlowState = "success"
	DeviceFlowExpired    DeviceFlowState = "expired"
	DeviceFlowDenied     DeviceFlowState = "denied"
	DeviceFlowError      DeviceFlowState = "error"
	DeviceFlowCanceled   DeviceFlowState = "canceled"
)

type DeviceFlowStatus

type DeviceFlowStatus struct {
	State DeviceFlowState `json:"state"`
	Error string          `json:"error,omitempty"`
}

DeviceFlowStatus is the response body for GET /auth/{provider}/device/status. Pure read: no side effects. The TUI polls this every couple seconds to drive its phase transitions and labels.

type ErrorData

type ErrorData struct {
	Message string `json:"message"`
}

ErrorData is the payload for EventError.

type Event

type Event struct {
	Type      EventType `json:"type"`
	SessionID string    `json:"session_id"` // hub session id (set by hub relay)
	// ExternalID carries the session-backend's native session ID. TODO rename
	ExternalID string      `json:"external_id,omitempty"`
	Timestamp  time.Time   `json:"timestamp"`
	Data       interface{} `json:"data"`
}

Event is the unified event type emitted by all backends and forwarded through the daemon to connected TUI clients.

func (*Event) UnmarshalJSON

func (e *Event) UnmarshalJSON(b []byte) error

UnmarshalJSON implements custom JSON unmarshalling for Event. It examines the "type" field to deserialize "data" into the correct concrete Go type instead of the default map[string]interface{}.

type EventType

type EventType string

EventType classifies daemon events.

const (
	EventStatusChange  EventType = "status"       // Session status changed
	EventMessage       EventType = "message"      // New message (user or assistant)
	EventPartUpdate    EventType = "part"         // Part updated (tool call progress, text delta)
	EventPermission    EventType = "permission"   // Agent requests permission for a tool
	EventError         EventType = "error"        // Error occurred
	EventTitleChange   EventType = "title"        // Session title updated
	EventModeChange    EventType = "mode"         // Agent-initiated session mode change (e.g. plan approval)
	EventRevertChange  EventType = "revert"       // Session revert state changed
	EventReconnecting  EventType = "reconnecting" // Backend is reconnecting to server
	EventReconnected   EventType = "reconnected"  // Backend successfully reconnected
	EventSessionCreate EventType = "session.create"
	EventSessionDelete EventType = "session.delete"
	EventMetaChange    EventType = "meta" // Session metadata changed (read state, visibility, draft, follow-up)

	// Voice events — emitted by the voice agent running on the daemon.
	EventVoiceTranscript EventType = "voice.transcript" // Model's spoken response as text
	EventVoiceStatus     EventType = "voice.status"     // Voice state changes (listening, thinking, speaking, idle)
	EventVoiceToolCall   EventType = "voice.tool_call"  // Voice agent called a tool
)

type ForkResult

type ForkResult struct {
	ID    string // External (backend) session ID
	Title string // AI-generated title carried over from the source session
}

ForkResult holds the result of a Fork operation.

type GitRef

type GitRef struct {
	LocalPath      string `json:"local_path,omitempty"`
	WorktreeID     string `json:"worktree_id,omitempty"`
	DisplayName    string `json:"display_name,omitempty"`
	WorktreeBranch string `json:"worktree_branch,omitempty"`
	Subdir         string `json:"subdir,omitempty"`
}

GitRef tells a host where to find a project's files.

Two locator fields, used per-host:

  • LocalPath: an absolute filesystem path on the *target host*. When the target host is co-located with the client (laptop TUI talking to laptop clankd), this is the user's repo path; the host opens it directly.
  • WorktreeID: the host-minted worktree ULID, stamped at `$(git rev-parse --absolute-git-dir)/clank/worktree-id` when the worktree is created (repo import, greenfield scaffold, or CreateRepoWorktree fork/load). It resolves to the repo-first linked worktree at `~/work/<WorktreeID>/` — a `git worktree` of the repo's bare canonical clone at ~/work/repos/<slug>/repo.git. A host that is *not* co-located (docker stack, cloud sandbox) ignores LocalPath and resolves by WorktreeID.

At least one MUST be set. Both is the common laptop pattern (TUI also sends WorktreeID so moving the session to a remote host later doesn't require re-creating it).

Resolution precedence on the host (see host.Service.workDirFor):

  1. If LocalPath is set and points at — or inside — a valid repo on this host → use it directly (the repo root for git/identity, the pointed-at directory as the cwd).
  2. Else if WorktreeID is set → use ~/work/<WorktreeID>/. Error if that directory doesn't exist (the worktree must have been created on this host first; we deliberately do not silently clone from origin — creation goes through the repo-first import/scaffold/fork paths, never a session-time fallback).
  3. Else → error.

DisplayName is an optional human-readable label set by the originating client (typically filepath.Base of LocalPath). UIs and logs use it; if empty, callers derive a label from the locator fields.

Subdir optionally narrows the *working* directory to a subdirectory of the repo, relative to the locator's root (LocalPath, the ~/work/<WorktreeID> worktree, or the WorktreeBranch worktree). Sessions and previews run there — e.g. a monorepo's web-app/ — while repo identity (RepoKey, persisted project_dir, branch/worktree ops) stays at the root. Hosts normalize a LocalPath that points inside a repo into {LocalPath: root, Subdir: rel} before persisting, so clients may simply send the folder they're in as LocalPath.

func (GitRef) Validate

func (g GitRef) Validate() error

Validate enforces: at least one of LocalPath / WorktreeID is set, LocalPath (when set) is absolute, Subdir (when set) is a local relative path (no absolute, no ".."-escape). WorktreeID format is not asserted here — the host's lookup of ~/work/<WorktreeID>/ catches invalid values via the underlying validRepoSlug check.

type LaunchHostSpec

type LaunchHostSpec struct {
	Provider string `json:"provider"` // "flymachines", "local-stub", ...
}

LaunchHostSpec asks the Hub to provision a fresh Host (sandbox) for this session before dispatching it. When set, the Hub consults a registered HostLauncher (e.g. "flymachines", "local-stub") which spins up a Host, registers it in the catalog, and rewrites Hostname to the launcher-chosen name.

Mutually exclusive with Hostname (Hostname is the launcher's output, not its input).

type MessageData

type MessageData struct {
	ID         string `json:"id,omitempty"` // Backend-assigned message ID (e.g. OpenCode message ID)
	Role       string `json:"role"`         // "user" or "assistant"
	Content    string `json:"content"`
	Parts      []Part `json:"parts,omitempty"`
	ModelID    string `json:"model_id,omitempty"`    // Model that produced this message (assistant only)
	ProviderID string `json:"provider_id,omitempty"` // Provider of the model (assistant only)
}

MessageData is the payload for EventMessage.

type MetaChangeData

type MetaChangeData struct {
	Session SessionInfo `json:"session"`
}

MetaChangeData is the payload for EventMetaChange. It carries the full post-mutation SessionInfo so subscribers can apply a single replacement instead of diffing per-field changes (read state, visibility, draft, follow-up, title, etc.).

type ModeChangeData

type ModeChangeData struct {
	ModeID string `json:"mode_id"`
}

ModeChangeData is the payload for EventModeChange: the agent changed its own session mode (client-requested changes ride SendMessageOpts Config instead). The host folds it into the session's persisted Config so a rehydrate restores the effective mode, not a stale one.

type ModeReporter

type ModeReporter interface {
	Modes() (currentID string, available []SessionMode)
}

ModeReporter is implemented by backends whose agent advertises session modes. The host stamps the result onto runtime SessionInfo so clients can render the agent-owned mode picker.

type ModelInfo

type ModelInfo struct {
	ID           string `json:"id"`            // Model ID (e.g. "claude-opus-4-20250514")
	Name         string `json:"name"`          // Human-readable name (e.g. "Claude Opus")
	ProviderID   string `json:"provider_id"`   // Provider ID (e.g. "github-copilot")
	ProviderName string `json:"provider_name"` // Human-readable provider name
}

ModelInfo is a lightweight summary of an available LLM model, used by the TUI to display and cycle through models.

type ModelOverride

type ModelOverride struct {
	ModelID    string `json:"model_id"`
	ProviderID string `json:"provider_id"`
}

ModelOverride specifies a model+provider to use for a single message, overriding the backend's default.

type ModelReporter

type ModelReporter interface {
	Models() (currentID string, available []ModelInfo)
}

ModelReporter is implemented by backends whose agent advertises a model choice for the session. Stamped onto runtime SessionInfo alongside modes so a client can render the picker (and the active model) without a second round trip.

type Part

type Part struct {
	ID     string         `json:"id"`
	Type   PartType       `json:"type"`
	Text   string         `json:"text,omitempty"`
	Tool   string         `json:"tool,omitempty"` // Tool name if tool call/result
	Status PartStatus     `json:"status,omitempty"`
	Input  map[string]any `json:"input,omitempty"`  // Tool call arguments (e.g. filePath, command)
	Output string         `json:"output,omitempty"` // Tool result text

	// Question is backend-appended metadata marking this tool call as an
	// interactive question prompt (Claude AskUserQuestion, OpenCode question
	// tool), normalized so clients render structured UI without parsing
	// provider-specific input. Present on both streamed part events and
	// Messages() transcripts — the tool part is the single source of truth
	// for the prompt, so a client that reopens a session recovers it from
	// the ordinary history refetch. Reply via RespondQuestion using
	// Question.RequestID.
	Question *QuestionPrompt `json:"question,omitempty"`
}

Part represents a piece of an assistant message (text block, tool call, etc.).

type PartStatus

type PartStatus string

PartStatus tracks the lifecycle of a tool call.

const (
	PartPending   PartStatus = "pending"
	PartRunning   PartStatus = "running"
	PartCompleted PartStatus = "completed"
	PartFailed    PartStatus = "error"
)

type PartType

type PartType string

PartType classifies the content of a Part.

const (
	PartText       PartType = "text"
	PartToolCall   PartType = "tool_call"
	PartToolResult PartType = "tool_result"
	PartThinking   PartType = "thinking"
)

type PartUpdateData

type PartUpdateData struct {
	MessageID string `json:"message_id,omitempty"`
	Part      Part   `json:"part"`
	// IsDelta indicates this is an incremental text chunk (append to existing
	// content). When false, Part.Text is the authoritative full snapshot and
	// should replace whatever text the TUI has accumulated for this part.
	IsDelta bool `json:"is_delta,omitempty"`
}

PartUpdateData is the payload for EventPartUpdate.

type PendingPermissionsReporter

type PendingPermissionsReporter interface {
	// PendingPermissions returns the parked requests, oldest first.
	PendingPermissions() []PermissionData
}

PendingPermissionsReporter is implemented by backends that can snapshot the permission requests currently parked awaiting a user decision. The host serves the snapshot via GET /sessions/{id}/pending-permission so a client that (re)joins a session blocked on a prompt — and so never saw its EventPermission on the live stream — can still render and answer it.

type PermissionData

type PermissionData struct {
	RequestID   string `json:"request_id"`
	Tool        string `json:"tool"`
	Description string `json:"description"`
	// ToolUseID is the id of the tool_use block this prompt is gating, when the
	// backend can determine it. It lets a client correlate the prompt with the
	// tool-call card it already rendered (whose id is the tool_use id) instead
	// of guessing by tool name. Empty when the backend can't attribute it.
	ToolUseID string `json:"tool_use_id,omitempty"`
}

PermissionData is the payload for EventPermission.

type ProjectInfo

type ProjectInfo struct {
	ID       string `json:"id"`
	Worktree string `json:"worktree"`
}

ProjectInfo is a lightweight project summary from the OpenCode API.

type ProviderAuthInfo

type ProviderAuthInfo struct {
	ProviderID  string           `json:"provider_id"`
	DisplayName string           `json:"display_name"`
	AuthType    string           `json:"auth_type"`
	Backend     BackendType      `json:"backend"`
	Connected   bool             `json:"connected"`
	Source      string           `json:"source,omitempty"` // CredentialSource* when connected
	Prompts     []ProviderPrompt `json:"prompts,omitempty"`
}

ProviderAuthInfo is the snapshot a client gets from GET /auth/providers. AuthType selects which begin-flow the client dispatches to; Backend identifies which agent CLI actually consumes the credential (opencode reads its own auth.json; claude reads env vars set by clank's anthropic sink). The /auth/providers endpoint filters by Backend when the client sends `?backend=…` — otherwise surfacing both sets in one list confuses the user (e.g. "Anthropic (Claude subscription)" is meaningless if you're starting an opencode session, and "GitHub Copilot" is meaningless for claude).

type ProviderPrompt

type ProviderPrompt struct {
	Key         string `json:"key"`
	Message     string `json:"message"`
	Placeholder string `json:"placeholder,omitempty"`
}

ProviderPrompt describes one extra input field a provider needs beyond the API key itself. The TUI renders one textinput per prompt in order, then the key. Mirrors the prompt shape OpenCode plugins use (see packages/opencode/src/provider/auth.ts), trimmed to the "text" type — Phase 3 doesn't need select prompts yet.

type Question

type Question struct {
	Text        string `json:"text"`                   // full question text
	Header      string `json:"header,omitempty"`       // short label (chip/tag)
	MultiSelect bool   `json:"multi_select,omitempty"` // multiple options may be selected
	// AllowCustom reports whether a free-text answer is accepted. Tri-state:
	// nil means the provider didn't say and clients treat it as allowed (the
	// universal default). A pointer rather than a bool so an explicit false —
	// opencode's custom=false — survives omitempty and reaches clients.
	AllowCustom *bool            `json:"allow_custom,omitempty"`
	Options     []QuestionOption `json:"options"`
}

Question is one question within a QuestionPrompt.

func (Question) CustomAllowed

func (q Question) CustomAllowed() bool

CustomAllowed reports whether a free-text answer is accepted, treating an unspecified AllowCustom as allowed.

type QuestionAnswer

type QuestionAnswer struct {
	Selected []string `json:"selected,omitempty"` // labels of the chosen options
	Custom   string   `json:"custom,omitempty"`   // free-text answer
}

QuestionAnswer is the user's answer to one Question. Both fields empty means the user delegated that question back to the agent.

type QuestionOption

type QuestionOption struct {
	Label       string `json:"label"`
	Description string `json:"description,omitempty"`
}

QuestionOption is one selectable choice for a Question.

type QuestionPrompt

type QuestionPrompt struct {
	RequestID string     `json:"request_id"`
	Questions []Question `json:"questions"`
}

QuestionPrompt is the normalized question payload carried on a tool-call Part (see Part.Question). Clients render the questions and reply via RespondQuestion with one QuestionAnswer per question, in order; the backend owns translating the answers into its provider's transport. Whether the prompt is still answerable is positional, not flagged: it is when the tool part is the conversation's last content (nothing after it means nothing superseded it).

type ReconnectedData

type ReconnectedData struct {
	Attempts   int  `json:"attempts"`    // How many attempts it took
	URLChanged bool `json:"url_changed"` // Whether the server URL changed (new port)
}

ReconnectedData is the payload for EventReconnected.

type ReconnectingData

type ReconnectingData struct {
	Attempt int           `json:"attempt"` // Current retry attempt (1-based)
	Delay   time.Duration `json:"delay"`   // How long until the next retry
	Error   string        `json:"error"`   // The error that triggered the reconnect
	GaveUp  bool          `json:"gave_up"` // True if this is the final failure (no more retries)
}

ReconnectingData is the payload for EventReconnecting.

type ResolvedImage

type ResolvedImage = resolvedImage

ResolvedImage aliases resolvedImage for backend subpackages (internal/agent/acp); the fields are already exported.

func ResolveAttachments

func ResolveAttachments(ctx context.Context, atts []Attachment) ([]ResolvedImage, error)

ResolveAttachments is the exported entry to resolveAttachments for backend subpackages.

type RevertChangeData

type RevertChangeData struct {
	MessageID string `json:"message_id"` // The message ID from which onward is reverted; empty means unrevert
}

RevertChangeData is the payload for EventRevertChange.

type SearchParams

type SearchParams struct {
	Query      string            `json:"query,omitempty"`      // pipe-separated OR groups
	Since      time.Time         `json:"since,omitempty"`      // only sessions updated at or after this time
	Until      time.Time         `json:"until,omitempty"`      // only sessions updated before this time
	Visibility SessionVisibility `json:"visibility,omitempty"` // "" = active only, "all" = everything, "done"/"archived" = only that
}

SearchParams defines the parameters for searching sessions.

Query supports pipe-separated OR groups with space-separated AND terms within each group. For example, "auth bug|dark mode" matches sessions containing ("auth" AND "bug") OR ("dark" AND "mode"). Matching is case-insensitive and word-boundary-aware: each term must appear at the start of a word (e.g. "auth" matches "authentication" but "hey" does not match "they").

Since and Until filter on UpdatedAt. Both are optional; when omitted the corresponding bound is open.

type SendMessageOpts

type SendMessageOpts struct {
	Text  string         `json:"text"`
	Model *ModelOverride `json:"model,omitempty"` // Per-message model override; nil = use default
	// Config changes agent-advertised session config options before this
	// prompt dispatches: option id → value id, e.g. {"mode": "plan"}.
	// Omitted map or key means "no change" — the session keeps the state
	// it already has (sessions remember themselves). Values ride verbatim;
	// the agent skips ids it does not advertise.
	Config map[string]string `json:"config,omitempty"`
	// Attachments are images the client uploaded out-of-band; the backend
	// downloads each via its presigned GetURL and inlines it into the agent
	// (Claude base64 content block / OpenCode file part). Empty for text-only
	// messages.
	Attachments []Attachment `json:"attachments,omitempty"`
}

SendMessageOpts contains options for sending a follow-up message.

type ServerInfo

type ServerInfo struct {
	URL        string    `json:"url"`
	ProjectDir string    `json:"project_dir"`
	PID        int       `json:"pid"`
	StartedAt  time.Time `json:"started_at"`
}

ServerInfo is a snapshot of a running backend server process (e.g. an `opencode serve` instance). Used by debugging/status commands.

type SessionBackend

type SessionBackend interface {
	// Open establishes (or re-attaches to) the session and begins event
	// production into Events(). Idempotent — safe to call on an
	// already-open session.
	Open(ctx context.Context) error

	// Send dispatches a prompt to an Open session. Fast-fails if the
	// session is not open. Returns once the prompt is handed off to
	// the agent runtime, NOT when the LLM finishes.
	Send(ctx context.Context, opts SendMessageOpts) error

	// OpenAndSend is the new-session convenience: Open followed by Send.
	// Backends MAY fuse the two operations when their runtime supports
	// it (e.g. dispatching the prompt as part of session creation).
	OpenAndSend(ctx context.Context, opts SendMessageOpts) error

	// Abort signals the agent to interrupt the current turn. Best-effort:
	// returns once the signal has been delivered, not when the agent has
	// actually stopped. Observe StatusChange events for completion.
	Abort(ctx context.Context) error

	// Stop performs a graceful shutdown: closes the event channel,
	// terminates child processes, and releases resources. Blocks until
	// teardown completes. Safe to call multiple times.
	Stop() error

	// Events returns the event stream for this backend. The channel is
	// closed when the backend stops. All events for a session flow
	// through this channel
	Events() <-chan Event

	// Status returns the current session status snapshot. May change
	// concurrently; treat as a hint, not authoritative.
	Status() SessionStatus

	// SessionID returns the agent-assigned native session ID, or "" if
	// not yet known. Used by HTTP handlers to serialize ExternalID and
	// by discover for deduplication. Hub code MUST NOT poll this after
	// Open to persist the ID — use Event.ExternalID instead, which is
	// the single source of truth that survives daemon restarts.
	SessionID() string

	// Messages returns the on-disk transcript for this session. Reads
	// fresh from the backend's storage on each call (no in-memory
	// accumulation fallback). Returns (nil, nil) if no transcript
	// exists yet (e.g. session ID not learned, or backend doesn't
	// support history retrieval).
	Messages(ctx context.Context) ([]MessageData, error)

	// Fork creates a new session branched from the given message.
	// Returns the new session's external ID and title. Returns a typed
	// ErrUnsupported when the backend/agent does not advertise forking.
	Fork(ctx context.Context, messageID string) (ForkResult, error)

	// RespondPermission replies to a pending permission prompt.
	// allow=true sends "once", allow=false sends "reject". denyMessage is the
	// reason shown to the model when allow=false (empty for a default reason);
	// it is ignored when allow=true and by backends whose protocol has no
	// deny-reason field.
	RespondPermission(ctx context.Context, permissionID string, allow bool, denyMessage string) error
}

Lifecycle: NewBackend → Open (or OpenAndSend) → Send* → Abort? → Stop

Concurrency: all methods must be safe to call concurrently from multiple goroutines.

Event timing: backends emit events asynchronously via Events(). Method returns describe what their *return* signals — typically "request dispatched" — NOT when the agent has finished work. Observe completion via Events() (StatusChange to Idle) and ExternalID via Event.ExternalID.

Session-scoped configuration (workDir, resume external ID, host/server selection) is supplied to the constructor, not to these methods. The methods below carry only per-prompt data.

type SessionDiscoverer

type SessionDiscoverer interface {
	DiscoverSessions(ctx context.Context, seedDir string) ([]SessionSnapshot, error)
}

SessionDiscoverer is an optional interface that BackendManagers can implement to discover historical sessions from the underlying backend.

type SessionInfo

type SessionInfo struct {
	ID              string            `json:"id"`
	ExternalID      string            `json:"external_id,omitempty"` // Backend's native session ID (e.g. OpenCode session ID)
	Backend         BackendType       `json:"backend"`
	Status          SessionStatus     `json:"status"`
	Visibility      SessionVisibility `json:"visibility,omitempty"` // User-set: "", "done", or "archived"
	FollowUp        bool              `json:"follow_up,omitempty"`  // User-set flag to mark session for follow-up
	Hostname        string            `json:"hostname,omitempty"`   // Canonical identity: host (Phase 3); "local" by default.
	GitRef          GitRef            `json:"git_ref,omitempty"`    // Canonical identity: repo (LocalPath and/or WorktreeID; WorktreeBranch when set).
	Prompt          string            `json:"prompt"`
	Title           string            `json:"title,omitempty"` // AI-generated session title from OpenCode
	TicketID        string            `json:"ticket_id,omitempty"`
	Agent           string            `json:"agent,omitempty"`             // Current OpenCode agent (e.g. "build", "plan")
	Draft           string            `json:"draft,omitempty"`             // Unsent follow-up text the user was composing
	Config          map[string]string `json:"config,omitempty"`            // Last-applied session config (option id → value id): create/send config merged with agent-initiated mode changes. Re-asserted on rehydrate so a backend rebuild can't reset the session's mode.
	RevertMessageID string            `json:"revert_message_id,omitempty"` // When set, messages from this ID onward are reverted (hidden)
	ServerURL       string            `json:"server_url,omitempty"`        // Runtime-only: backend server URL (e.g. OpenCode serve endpoint). Not persisted.
	CurrentModeID   string            `json:"current_mode_id,omitempty"`   // Runtime-only: the agent-owned session mode currently active. Not persisted.
	AvailableModes  []SessionMode     `json:"available_modes,omitempty"`   // Runtime-only: agent-advertised modes for the picker. Not persisted.
	CurrentModelID  string            `json:"current_model_id,omitempty"`  // Runtime-only: the agent-selected model for this session. Not persisted.
	AvailableModels []ModelInfo       `json:"available_models,omitempty"`  // Runtime-only: agent-advertised models for the picker. Not persisted.
	ConfigOptions   []ConfigOption    `json:"config_options,omitempty"`    // Runtime-only: the agent's full advertised config options (mode/model/effort/…), for knob editors. Not persisted.
	IsRemote        bool              `json:"is_remote,omitempty"`         // Runtime-only: decoration stamped by the laptop daemon's session router when this session's worktree is owned by the active remote. Always false on direct host responses; populated by gateway routing.
	CreatedAt       time.Time         `json:"created_at"`
	UpdatedAt       time.Time         `json:"updated_at"`
	LastReadAt      time.Time         `json:"last_read_at,omitempty"`
}

SessionInfo is a snapshot of a managed session, returned by the daemon API.

func (SessionInfo) Hidden

func (s SessionInfo) Hidden() bool

Hidden returns true if the session should not appear in the default inbox view.

func (SessionInfo) Unread

func (s SessionInfo) Unread() bool

Unread returns true if the session has activity the user hasn't seen.

type SessionMode

type SessionMode struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
}

type SessionSnapshot

type SessionSnapshot struct {
	ID              string      `json:"id"`
	Backend         BackendType `json:"backend"`
	Title           string      `json:"title"`
	Directory       string      `json:"directory"`
	RevertMessageID string      `json:"revert_message_id,omitempty"`
	CreatedAt       time.Time   `json:"created_at"`
	UpdatedAt       time.Time   `json:"updated_at"`
}

SessionSnapshot is a lightweight session summary returned by a backend manager's DiscoverSessions, used to populate the daemon's session list.

Backend identifies which backend produced the snapshot. The hub aggregates snapshots from multiple backends in a single slice, so without this field it is impossible to attribute a snapshot to its source backend at the registration site. Persisting the wrong Backend on a discovered session causes activateBackend (after a daemon restart) to route the reopen through the wrong backend manager, which manifests as a permanent "Waiting for agent output..." hang for Claude sessions that were mis-tagged as opencode.

type SessionStatus

type SessionStatus string

SessionStatus represents the current state of an agent session.

const (
	StatusStarting SessionStatus = "starting" // Process is launching
	StatusBusy     SessionStatus = "busy"     // Agent is actively working
	StatusIdle     SessionStatus = "idle"     // Agent finished, awaiting input
	StatusError    SessionStatus = "error"    // Agent encountered an error
	StatusDead     SessionStatus = "dead"     // Process exited
)

type SessionVisibility

type SessionVisibility string

SessionVisibility controls whether a session appears in the default inbox view. It is orthogonal to SessionStatus — a session can be idle at the system level but marked "done" by the user.

const (
	VisibilityVisible  SessionVisibility = ""         // Default: shown in inbox
	VisibilityDone     SessionVisibility = "done"     // User marked as completed
	VisibilityArchived SessionVisibility = "archived" // User archived (won't do / irrelevant)
	VisibilityAll      SessionVisibility = "all"      // Pseudo-value: include all visibilities
)

type SoftwareInfo

type SoftwareInfo struct {
	Version string `json:"version"`
}

SoftwareInfo describes one tool clank-host knows about. Version is empty when the tool isn't installed (or failed to respond to --version) — callers should treat empty as "unavailable" rather than treating absence of a record. Future fields (path, install method, install_time, etc.) can be added without breaking the wire shape.

type SoftwareManifest

type SoftwareManifest struct {
	OpenCode SoftwareInfo `json:"opencode"`
	Claude   SoftwareInfo `json:"claude"`
}

SoftwareManifest is what GET /software-manifest returns. JSON keys are snake_case to match the rest of clank's wire conventions.

func GetSoftwareManifest

func GetSoftwareManifest(ctx context.Context) SoftwareManifest

GetSoftwareManifest returns the probed manifest, computing it lazily on first call. Concurrent first-callers serialize on sync.Once; once cached, reads are lock-free.

ctx is accepted for symmetry with cancellable callers but is NOT plumbed into the probe — the probe runs on a private softwareManifestProbeTimeout context so a canceled first request can't permanently cache an empty manifest. If you need an uncached probe (e.g. to detect an out-of-band opencode upgrade), the right answer is to restart clank-host.

type StartRequest

type StartRequest struct {
	Backend    BackendType     `json:"backend"`
	Hostname   string          `json:"hostname,omitempty"`    // Target host; empty defaults to "local" at the hub.
	LaunchHost *LaunchHostSpec `json:"launch_host,omitempty"` // When set, Hub provisions a fresh host before dispatching.
	GitRef     GitRef          `json:"git_ref"`               // Wire-canonical repo identity; required. WorktreeBranch lives inside.
	Prompt     string          `json:"prompt"`
	SessionID  string          `json:"session_id,omitempty"` // Backend-external session ID for resume; empty = new session.
	TicketID   string          `json:"ticket_id,omitempty"`  // Optional backlog ticket link
	Model      *ModelOverride  `json:"model,omitempty"`      // Per-message model override; nil = use default
	// Config sets agent-advertised session config options at creation:
	// option id → value id from that option's advertised list, e.g.
	// {"mode": "plan", "effort": "high"}. The host validates that every
	// key of the backend's built-in Default preset is present — it never
	// fills values in (no fallbacks; a missing key fails the create) —
	// and passes values through verbatim: the agent owns the vocabulary
	// and skips ids it does not advertise.
	Config map[string]string `json:"config,omitempty"`
	// Attachments are images for the first message, forwarded to the backend's
	// OpenAndSend. Same semantics as SendMessageOpts.Attachments.
	Attachments []Attachment `json:"attachments,omitempty"`
}

StartRequest contains the parameters needed to start a new agent session.

Identity is path-free post Phase 3D-2 (hub_host_refactor.md): (Hostname, GitRef, WorktreeBranch). The Host resolves these to a working directory inside CreateSession; the wire never carries filesystem paths.

GitRef is the sole repo identity on the wire (§7.3 of hub_host_refactor_code_review.md).

func (StartRequest) Validate

func (r StartRequest) Validate() error

Validate checks that required fields are set per §7.3 of hub_host_refactor_code_review.md.

type StatusChangeData

type StatusChangeData struct {
	OldStatus SessionStatus `json:"old_status"`
	NewStatus SessionStatus `json:"new_status"`
}

StatusChangeData is the payload for EventStatusChange.

type TitleChangeData

type TitleChangeData struct {
	Title string `json:"title"`
}

TitleChangeData is the payload for EventTitleChange.

type VoiceStatus

type VoiceStatus string

VoiceStatus represents the voice agent's current state.

const (
	VoiceStatusIdle      VoiceStatus = "idle"      // No voice session active
	VoiceStatusListening VoiceStatus = "listening" // Mic is live, user is speaking
	VoiceStatusThinking  VoiceStatus = "thinking"  // Audio committed, waiting for model
	VoiceStatusSpeaking  VoiceStatus = "speaking"  // Model is producing audio response
)

type VoiceStatusData

type VoiceStatusData struct {
	Status VoiceStatus `json:"status"`
}

VoiceStatusData is the payload for EventVoiceStatus.

type VoiceToolCallData

type VoiceToolCallData struct {
	Name   string `json:"name"`             // Tool name (e.g. "list_sessions")
	Args   string `json:"args,omitempty"`   // JSON-encoded arguments
	Result string `json:"result,omitempty"` // Tool result (empty if still running)
}

VoiceToolCallData is the payload for EventVoiceToolCall.

type VoiceTranscriptData

type VoiceTranscriptData struct {
	Text string `json:"text"`           // Incremental or final transcript text
	Done bool   `json:"done,omitempty"` // True when transcript is final
}

VoiceTranscriptData is the payload for EventVoiceTranscript.

Directories

Path Synopsis
acp
Package acp adapts Agent Client Protocol (ACP) agents to clank's SessionBackend seam.
Package acp adapts Agent Client Protocol (ACP) agents to clank's SessionBackend seam.
acptest
Package acptest provides an in-process scripted ACP agent for tests: a real agent speaking real JSON-RPC over real pipes through the same SDK clank uses — the protocol-level analog of hosttest.StubBackend.
Package acptest provides an in-process scripted ACP agent for tests: a real agent speaking real JSON-RPC over real pipes through the same SDK clank uses — the protocol-level analog of hosttest.StubBackend.
Package acptools provisions the pinned ACP adapter packages onto a host.
Package acptools provisions the pinned ACP adapter packages onto a host.
Package guidance assembles the stack-specific guidance that clank injects as the building agent's system prompt at session start, and materializes the stack's detailed playbook as an on-demand skill in the user's personal skills directory (~/.claude/skills).
Package guidance assembles the stack-specific guidance that clank injects as the building agent's system prompt at session start, and materializes the stack's detailed playbook as an on-demand skill in the user's personal skills directory (~/.claude/skills).
Package presets defines agent presets: named bundles of session config values (mode, model, effort, …) a client applies when creating or steering a session.
Package presets defines agent presets: named bundles of session config values (mode, model, effort, …) a client applies when creating or steering a session.

Jump to

Keyboard shortcuts

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