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
- Variables
- func ClaudeVersion(ctx context.Context) (string, error)
- func DataURL(mime string, data []byte) string
- func GitDir(projectDir string) (string, error)
- func IsAnyProviderConnected(providers []ProviderAuthInfo) bool
- func IsBackendConnected(providers []ProviderAuthInfo, backend BackendType) bool
- func OpenCodeVersion(ctx context.Context) (string, error)
- func OpencodeVersionAtLeast(v, floor string) (bool, error)
- func ParseClaudeVersionOutput(out string) string
- func ParseTimeParam(s string) (time.Time, error)
- func ReadLocalWorktreeID(projectDir string) (string, error)
- func RemoveLocalWorktreeID(projectDir string) (bool, error)
- func RepoDisplayName(g GitRef) string
- func RepoKey(g GitRef) string
- func WriteLocalWorktreeID(projectDir, id string) error
- type AgentInfo
- type AgentLister
- type AllSessionDiscoverer
- type Attachment
- type AuthCredential
- type BackendInvocation
- type BackendManager
- type BackendType
- type ClaudePermissionMode
- type ConfigOption
- type ConfigOptionValue
- type ConfigOptionsLister
- type ConfigOptionsReporter
- type DeviceFlowStart
- type DeviceFlowState
- type DeviceFlowStatus
- type ErrorData
- type Event
- type EventType
- type ForkResult
- type GitRef
- type LaunchHostSpec
- type MessageData
- type MetaChangeData
- type ModeChangeData
- type ModeReporter
- type ModelInfo
- type ModelOverride
- type ModelReporter
- type Part
- type PartStatus
- type PartType
- type PartUpdateData
- type PendingPermissionsReporter
- type PermissionData
- type ProjectInfo
- type ProviderAuthInfo
- type ProviderPrompt
- type Question
- type QuestionAnswer
- type QuestionOption
- type QuestionPrompt
- type ReconnectedData
- type ReconnectingData
- type ResolvedImage
- type RevertChangeData
- type SearchParams
- type SendMessageOpts
- type ServerInfo
- type SessionBackend
- type SessionDiscoverer
- type SessionInfo
- type SessionMode
- type SessionSnapshot
- type SessionStatus
- type SessionVisibility
- type SoftwareInfo
- type SoftwareManifest
- type StartRequest
- type StatusChangeData
- type TitleChangeData
- type VoiceStatus
- type VoiceStatusData
- type VoiceToolCallData
- type VoiceTranscriptData
Constants ¶
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" )
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.
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.
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).
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.
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.
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`.
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:
- Update the constant.
- `make install` — laptops get the new clank that knows the new pin.
- 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.
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:
- Update the constant (and re-verify the `opencode acp` surface).
- `make install` — laptops get the new clank that knows the new pin.
- Sprites probe-and-reinstall on next EnsureHost (~30-90s one-shot cost).
- Laptops below the floor see the upgrade hint at first opencode use.
Variables ¶
var AllBackends = []BackendType{ BackendOpenCode, BackendClaudeCode, BackendCodex, }
AllBackends lists every backend the daemon knows how to launch, in a stable display order. Used by the settings UI to cycle / pick.
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.
var ClaudePermissionModes = []ClaudePermissionMode{ ClaudePermDefault, ClaudePermAcceptEdits, ClaudePermPlan, ClaudePermBypass, ClaudePermAuto, }
ClaudePermissionModes is the ordered set the TUI cycles through (Tab).
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ReadLocalWorktreeID returns the worktree ULID cached for projectDir:
- $CLANK_WORKTREE_ID if non-empty.
- <gitDir>/clank/worktree-id (where gitDir = git rev-parse --absolute-git-dir).
- "" 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 ¶
RemoveLocalWorktreeID deletes the stamped worktree ULID for projectDir. Returns true if an id was present.
func RepoDisplayName ¶
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 ¶
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 ¶
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 ¶
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):
- 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).
- 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).
- 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 ¶
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 ¶
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 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 ¶
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 ¶
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 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.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
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. |