commentbus

package
v0.0.0-...-db76fb6 Latest Latest
Warning

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

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 38 Imported by: 0

Documentation

Index

Constants

View Source
const (

	// AutoUpdateResult* are the terminal outcomes recorded by startup
	// reconciliation and surfaced as last_update_result in health.
	AutoUpdateResultNone       = "none"
	AutoUpdateResultSuccess    = "success"
	AutoUpdateResultRolledBack = "rolled_back"
)
View Source
const (
	// EnvProduction and EnvStaging are the two supported environment names.
	EnvProduction = "production"
	EnvStaging    = "staging"

	// EnvVar is the canonical environment selector. The CLI entry point sets
	// it from --staging/--production, the invoked binary name, or an inherited
	// value; everything downstream reads it via CurrentEnvironment.
	EnvVar = "COMMENT_IO_ENV"

	// BaseURLEnvVar overrides the default base URL for both environments.
	BaseURLEnvVar = "COMMENT_IO_BASE_URL"

	// StagingBaseURLEnvVar overrides the staging default base URL only.
	StagingBaseURLEnvVar = "COMMENT_IO_STAGING_BASE_URL"
)
View Source
const (
	BusProtocolVersion                    = 1
	FeatureBotletsSetupOrientation        = "botlets_setup_orientation"
	FeatureBotletsSetupOrientationVersion = "multiline-v1"
	// Daemon-mediated agent enrollment feature bits. Defined ahead of the
	// subsystems shipping; not advertised in the daemon health response until
	// pairing (Phase 2) and enrollment (Phase 3) actually exist.
	FeatureDaemonPairing          = "daemon_pairing"
	FeatureDaemonPairingVersion   = 1
	FeatureAgentEnrollment        = "agent_enrollment"
	FeatureAgentEnrollmentVersion = 1
)
View Source
const (
	BotletsRepairHintPathLabelMismatch     = "PATH_LABEL_MISMATCH"
	BotletsRepairHintCanonicalProfileMoved = "CANONICAL_PROFILE_RENAMED"
	BotletsRepairHintSyncPathMovePending   = "SYNC_PATH_MOVE_PENDING"
)
View Source
const (
	RuntimeRoleMain = "main"
	RuntimeRoleTask = "task"
)
View Source
const (
	RuntimeLaunchModePath  = "path"
	RuntimeLaunchModeShell = "shell"
)
View Source
const (
	SessionHostTmux = "tmux"
	SessionHostBmux = "bmux"
)
View Source
const (
	BmuxBinaryEnv = "COMMENT_IO_BMUX_BIN"
)
View Source
const (

	// BmuxInstallScriptURL is the one-line installer users run by hand to install
	// bmux, the explicit opt-in runtime host. bmux is no longer auto-installed by
	// `comment bus install` / `comment doctor --fix`, so this is the install path.
	BmuxInstallScriptURL = "https://raw.githubusercontent.com/" + bmuxDistRepo + "/main/install.sh"
)
View Source
const BotletsBootstrapFileName = "BOOTSTRAP.md"

BotletsBootstrapFileName is the brain file used only during setup orientation. Its presence is what gates whether the agent runs bootstrap; there is no separate server-side "already bootstrapped" flag.

View Source
const DaemonAuthFileName = "daemon-auth.json"

DaemonAuthFileName is the daemon pairing credential file kept under the private bus directory (`<home>/bus/daemon-auth.json`).

View Source
const SQLiteSchemaVersion = 5
View Source
const SocketErrorCodeBmuxNotInstalled = "BMUX_NOT_INSTALLED"

SocketErrorCodeBmuxNotInstalled is the local-bus socket error code the daemon returns when it cannot launch a runtime because bmux is missing. The CLI maps it to a dedicated, human-readable exit status. Additive: older clients that don't recognize it still print the accompanying message and exit non-zero.

View Source
const SocketErrorCodeTmuxNotInstalled = "TMUX_NOT_INSTALLED"

SocketErrorCodeTmuxNotInstalled is the local-bus socket error code the daemon returns when it cannot launch a runtime because tmux is missing. The CLI maps it to a dedicated, human-readable exit status. Additive: older clients that don't recognize it still print the accompanying message and exit non-zero.

View Source
const TmuxBinaryEnv = "COMMENT_IO_TMUX_BIN"

TmuxBinaryEnv pins the tmux binary the daemon uses. Set it to an absolute path to a known-good tmux to bypass discovery entirely.

Variables

View Source
var (
	LocalMessageIDRE           = regexp.MustCompile(`^msg_[A-Za-z0-9_-]{20,64}$`)
	LocalEventIDRE             = regexp.MustCompile(`^evt_[A-Za-z0-9_-]{20,64}$`)
	LocalOperationIDRE         = regexp.MustCompile(`^op_[A-Za-z0-9_-]{20,64}$`)
	LocalSessionIDRE           = regexp.MustCompile(`^sess_[A-Za-z0-9_-]{20,64}$`)
	LocalSessionGenerationIDRE = regexp.MustCompile(`^gen_[A-Za-z0-9_-]{16,64}$`)
	BotNameRE                  = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,62}$`)
	ProfileRE                  = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]\.[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$`)
	DocSlugRE                  = regexp.MustCompile(`^[a-z0-9]{3,64}$`)
	UUIDRE                     = regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`)
	UUIDLikeRE                 = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
)
View Source
var (
	ErrMessageNotFound = errors.New("message not found")
	ErrMessageConflict = errors.New("message conflict")
)
View Source
var (
	ErrMissingAgentCredential = errors.New("missing agent credential")
	ErrInvalidAgentHandle     = errors.New("invalid agent handle")
	ErrInvalidAgentRuntime    = errors.New("invalid runtime")
	ErrInvalidAgentModel      = errors.New("invalid model")
)

Sentinel errors for PrepareAgentProfileWrite validation failures. Callers that need product-specific phrasing (e.g. Botlets) can map these with errors.Is.

View Source
var (
	ErrInvalidSession       = errors.New("invalid session")
	ErrSessionAlreadyExists = errors.New("session already exists")
)
View Source
var (
	TmuxSessionNameRE = regexp.MustCompile(`^comment-[a-z0-9-]{3,80}$`)
	TmuxPaneTargetRE  = regexp.MustCompile(`^(?:%[0-9]{1,10}|comment-[a-z0-9-]{3,80}:[0-9]{1,3}\.[0-9]{1,3})$`)

	ErrTmuxSessionMissing = errors.New("tmux session is not running")
)
View Source
var BotletsSetupStartupFileNames = []struct {
	Name        string
	Description string
}{
	{"AGENTS.md", "workspace rules and operating guidance"},
	{"TOOLS.md", "local tool/setup notes"},
	{"SOUL.md", "persona, tone, and behavioral guidance"},
	{"IDENTITY.md", "bot identity"},
	{"USER.md", "owner/user profile"},
	{"MEMORY.md", "curated long-term bot memory"},
	{"HEARTBEAT.md", "recurring heartbeat and cron task instructions"},
}

BotletsSetupStartupFileNames are loaded for the owner-facing setup/main session. This includes MEMORY.md so durable bot memory stays in the Comment.io brain rather than the runtime's private memory feature.

View Source
var BotletsStartupFileNames = BotletsTaskStartupFileNames

BotletsStartupFileNames is retained for existing callers that expect the task-oriented startup list.

View Source
var BotletsTaskStartupFileNames = []struct {
	Name        string
	Description string
}{
	{"AGENTS.md", "workspace rules and operating guidance"},
	{"TOOLS.md", "local tool/setup notes"},
	{"SOUL.md", "persona, tone, and behavioral guidance"},
	{"IDENTITY.md", "bot identity"},
	{"USER.md", "owner/user profile"},
	{"HEARTBEAT.md", "recurring heartbeat and cron task instructions"},
}

BotletsTaskStartupFileNames are the bot brain files the agent should orient from at the start of scheduled/task Botlets runs. Order matches OpenClaw cron startup behavior and intentionally excludes MEMORY.md by default. Scheduled runs also intentionally do NOT read the today/yesterday daily notes that the interactive/main session reads (see BuildBotletsSetupOrientation and BotletsSetupStartupFileNames): a cron run is isolated from session history by design, so it orients only from the durable task-facing brain files plus HEARTBEAT.md. Continuity across restarts is a main-session concern.

View Source
var CapabilityTokenRE = regexp.MustCompile(`^cap_[A-Za-z0-9_-]{20,128}$`)
View Source
var (

	// ErrAgentAuthRevoked is returned when the notification wake socket is
	// closed by the server with close code 4431 (WS_AGENT_AUTH_REVOKED_CLOSE_CODE
	// in packages/shared/src/protocol.ts). Reconnecting with the same agent
	// credentials will be rejected again; callers should back off and prompt
	// the operator to re-issue credentials.
	ErrAgentAuthRevoked = errors.New("notification wake socket closed: agent auth revoked")
)
View Source
var ErrBmuxNotInstalled = errors.New("bmux is not installed")

ErrBmuxNotInstalled marks the specific failure where the bmux binary cannot be located at all — it is not present in any trusted directory and no usable COMMENT_IO_BMUX_BIN pin is set. It is distinct from "a configured bmux pin is unusable" so callers can surface a clear install message (and the daemon can auto-install) instead of an opaque launch failure. Detect it with errors.Is.

View Source
var ErrCapabilityFileTooLarge = errors.New("capability file too large")
View Source
var ErrCapabilityFileUnsafe = errors.New("capability file unsafe")
View Source
var ErrStoreNotInitialized = errors.New("comment bus sqlite history is not initialized")
View Source
var ErrTmuxNotInstalled = errors.New("tmux is not installed")

ErrTmuxNotInstalled marks the specific failure where the tmux binary cannot be located at all — it is not installed on a standard path and no usable COMMENT_IO_TMUX_BIN pin is set. It is distinct from "a configured tmux pin is unusable" so callers can surface a clear, OS-appropriate install message instead of a generic launch failure. Detect it with errors.Is.

View Source
var (
	ListenSessionTokenRE = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`)
)
View Source
var SQLiteSchemaV1 = []string{
	`CREATE TABLE IF NOT EXISTS messages (
		id TEXT PRIMARY KEY,
		source TEXT NOT NULL,
		kind TEXT NOT NULL,
		thread_id TEXT,
		sender TEXT NOT NULL,
		profile TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		body_format TEXT NOT NULL,
		body_content TEXT NOT NULL,
		refs_json TEXT NOT NULL,
		created_at TEXT NOT NULL,
		retention_bucket TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS message_recipients (
		message_id TEXT NOT NULL,
		profile TEXT NOT NULL,
		handle TEXT NOT NULL,
		delivery_state TEXT NOT NULL,
		claim_holder TEXT,
		lease_expires_at TEXT,
		session_id TEXT,
		session_scope_type TEXT,
		session_scope_id TEXT,
		session_generation TEXT,
		read_at TEXT,
		PRIMARY KEY (message_id, profile)
	)`,
	`CREATE TABLE IF NOT EXISTS events (
		id TEXT PRIMARY KEY,
		message_id TEXT,
		profile TEXT,
		event_type TEXT NOT NULL,
		redacted_json TEXT NOT NULL,
		created_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS sessions (
		session_id TEXT PRIMARY KEY,
		profile TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		scope_type TEXT NOT NULL,
		scope_id TEXT NOT NULL,
		tmux_session TEXT NOT NULL,
		pane_target TEXT NOT NULL,
		generation TEXT NOT NULL,
		runtime TEXT NOT NULL,
		state TEXT NOT NULL,
		last_nudge_json TEXT NOT NULL,
		created_at TEXT NOT NULL,
		updated_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS outbox (
		idempotency_key TEXT PRIMARY KEY,
		sender_profile TEXT NOT NULL,
		recipient_profiles_json TEXT NOT NULL,
		state TEXT NOT NULL,
		created_at TEXT NOT NULL,
		updated_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS transient_runtimes (
		run_id TEXT PRIMARY KEY,
		profile TEXT NOT NULL,
		role TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		session_name TEXT NOT NULL,
		pane_target TEXT NOT NULL,
		runtime TEXT NOT NULL,
		runtime_command_json TEXT NOT NULL,
		runtime_command_path TEXT NOT NULL,
		comment_command_path TEXT NOT NULL,
		output_log_path TEXT NOT NULL,
		runtime_path TEXT NOT NULL,
		cwd TEXT NOT NULL,
		state TEXT NOT NULL,
		started_at TEXT NOT NULL
	)`,
	`CREATE INDEX IF NOT EXISTS idx_transient_runtimes_profile_role ON transient_runtimes(profile, role)`,
}
View Source
var SQLiteSchemaV2 = []string{
	`CREATE TABLE IF NOT EXISTS messages (
		id TEXT PRIMARY KEY,
		source TEXT NOT NULL,
		kind TEXT NOT NULL,
		thread_id TEXT,
		sender TEXT NOT NULL,
		profile TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		body_format TEXT NOT NULL,
		body_content TEXT NOT NULL,
		refs_json TEXT NOT NULL,
		created_at TEXT NOT NULL,
		retention_bucket TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS message_recipients (
		message_id TEXT NOT NULL,
		profile TEXT NOT NULL,
		handle TEXT NOT NULL,
		delivery_state TEXT NOT NULL,
		claim_holder TEXT,
		lease_expires_at TEXT,
		session_id TEXT,
		session_scope_type TEXT,
		session_scope_id TEXT,
		session_generation TEXT,
		read_at TEXT,
		PRIMARY KEY (message_id, profile)
	)`,
	`CREATE TABLE IF NOT EXISTS events (
		id TEXT PRIMARY KEY,
		message_id TEXT,
		profile TEXT,
		event_type TEXT NOT NULL,
		redacted_json TEXT NOT NULL,
		created_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS sessions (
		session_id TEXT PRIMARY KEY,
		profile TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		scope_type TEXT NOT NULL,
		scope_id TEXT NOT NULL,
		tmux_session TEXT NOT NULL,
		pane_target TEXT NOT NULL,
		generation TEXT NOT NULL,
		runtime TEXT NOT NULL,
		state TEXT NOT NULL,
		last_nudge_json TEXT NOT NULL,
		created_at TEXT NOT NULL,
		updated_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS outbox (
		idempotency_key TEXT PRIMARY KEY,
		sender_profile TEXT NOT NULL,
		recipient_profiles_json TEXT NOT NULL,
		state TEXT NOT NULL,
		created_at TEXT NOT NULL,
		updated_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS transient_runtimes (
		run_id TEXT PRIMARY KEY,
		host TEXT NOT NULL DEFAULT 'tmux',
		profile TEXT NOT NULL,
		role TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		session_name TEXT NOT NULL,
		pane_target TEXT NOT NULL,
		runtime TEXT NOT NULL,
		runtime_command_json TEXT NOT NULL,
		runtime_command_path TEXT NOT NULL,
		comment_command_path TEXT NOT NULL,
		output_log_path TEXT NOT NULL,
		runtime_path TEXT NOT NULL,
		cwd TEXT NOT NULL,
		state TEXT NOT NULL,
		started_at TEXT NOT NULL
	)`,
	`CREATE INDEX IF NOT EXISTS idx_transient_runtimes_profile_role ON transient_runtimes(profile, role)`,
}
View Source
var SQLiteSchemaV3 = []string{
	`CREATE TABLE IF NOT EXISTS messages (
		id TEXT PRIMARY KEY,
		source TEXT NOT NULL,
		kind TEXT NOT NULL,
		thread_id TEXT,
		sender TEXT NOT NULL,
		profile TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		bot_id TEXT NOT NULL DEFAULT '',
		bot_agent_id TEXT NOT NULL DEFAULT '',
		body_format TEXT NOT NULL,
		body_content TEXT NOT NULL,
		refs_json TEXT NOT NULL,
		created_at TEXT NOT NULL,
		retention_bucket TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS message_recipients (
		message_id TEXT NOT NULL,
		profile TEXT NOT NULL,
		handle TEXT NOT NULL,
		bot_id TEXT NOT NULL DEFAULT '',
		bot_agent_id TEXT NOT NULL DEFAULT '',
		delivery_state TEXT NOT NULL,
		claim_holder TEXT,
		lease_expires_at TEXT,
		session_id TEXT,
		session_scope_type TEXT,
		session_scope_id TEXT,
		session_generation TEXT,
		read_at TEXT,
		PRIMARY KEY (message_id, profile)
	)`,
	`CREATE TABLE IF NOT EXISTS events (
		id TEXT PRIMARY KEY,
		message_id TEXT,
		profile TEXT,
		event_type TEXT NOT NULL,
		redacted_json TEXT NOT NULL,
		created_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS sessions (
		session_id TEXT PRIMARY KEY,
		profile TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		bot_id TEXT NOT NULL DEFAULT '',
		bot_agent_id TEXT NOT NULL DEFAULT '',
		scope_type TEXT NOT NULL,
		scope_id TEXT NOT NULL,
		tmux_session TEXT NOT NULL,
		pane_target TEXT NOT NULL,
		generation TEXT NOT NULL,
		runtime TEXT NOT NULL,
		state TEXT NOT NULL,
		last_nudge_json TEXT NOT NULL,
		created_at TEXT NOT NULL,
		updated_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS outbox (
		idempotency_key TEXT PRIMARY KEY,
		sender_profile TEXT NOT NULL,
		sender_bot_id TEXT NOT NULL DEFAULT '',
		sender_bot_agent_id TEXT NOT NULL DEFAULT '',
		recipient_profiles_json TEXT NOT NULL,
		state TEXT NOT NULL,
		created_at TEXT NOT NULL,
		updated_at TEXT NOT NULL
	)`,
	`CREATE TABLE IF NOT EXISTS transient_runtimes (
		run_id TEXT PRIMARY KEY,
		host TEXT NOT NULL DEFAULT 'tmux',
		bmux_binary TEXT NOT NULL DEFAULT '',
		profile TEXT NOT NULL,
		role TEXT NOT NULL,
		bot_name TEXT NOT NULL,
		bot_id TEXT NOT NULL DEFAULT '',
		bot_agent_id TEXT NOT NULL DEFAULT '',
		session_name TEXT NOT NULL,
		pane_target TEXT NOT NULL,
		runtime TEXT NOT NULL,
		runtime_command_json TEXT NOT NULL,
		runtime_command_path TEXT NOT NULL,
		comment_command_path TEXT NOT NULL,
		output_log_path TEXT NOT NULL,
		runtime_path TEXT NOT NULL,
		cwd TEXT NOT NULL,
		state TEXT NOT NULL,
		started_at TEXT NOT NULL,
		runtime_launch_mode TEXT NOT NULL DEFAULT 'path'
	)`,
	`CREATE INDEX IF NOT EXISTS idx_transient_runtimes_profile_role ON transient_runtimes(profile, role)`,
	`CREATE INDEX IF NOT EXISTS idx_messages_bot_identity ON messages(bot_id, bot_agent_id)`,
	`CREATE INDEX IF NOT EXISTS idx_message_recipients_bot_identity ON message_recipients(bot_id, bot_agent_id)`,
	`CREATE INDEX IF NOT EXISTS idx_sessions_bot_identity ON sessions(bot_id, bot_agent_id)`,
	`CREATE INDEX IF NOT EXISTS idx_outbox_sender_bot_identity ON outbox(sender_bot_id, sender_bot_agent_id)`,
	`CREATE INDEX IF NOT EXISTS idx_transient_runtimes_bot_identity ON transient_runtimes(bot_id, bot_agent_id)`,
}

Functions

func AbandonCloudNotificationClaimOperation

func AbandonCloudNotificationClaimOperation(paths Paths, op CloudNotificationClaimOperation) error

func AbandonPendingCloudNotificationRenewOperations

func AbandonPendingCloudNotificationRenewOperations(paths Paths, ops []CloudNotificationClaimOperation) error

func AutoUpdateHealth

func AutoUpdateHealth(paths Paths, currentVersion string) map[string]any

AutoUpdateHealth builds the auto-update fields for the health surfaces. It reads only the cached state file — the worker (which owns version parsing) has already computed update_available — so this stays free of any version comparison logic.

func BmuxClientEnv

func BmuxClientEnv(base []string, tokenFile string) []string

func BmuxDaemonDiscoverable

func BmuxDaemonDiscoverable() bool

BmuxDaemonDiscoverable reports whether an unpinned launchd/systemd daemon would resolve bmux. Passing the bare name explicitly bypasses the env pin that TrustedBmuxBinaryPath("") honors, so this reflects the daemon's real view (trusted directories only, no shell environment).

func BmuxInstallHintShort

func BmuxInstallHintShort() string

BmuxInstallHintShort returns the single-line command that installs bmux, suitable for compact contexts like `comment doctor` JSON output. bmux is a first-party binary we publish, so unlike tmux there is one canonical installer rather than a per-distro package name.

func BmuxNotInstalledMessage

func BmuxNotInstalledMessage() string

BmuxNotInstalledMessage builds the full, human-readable error explaining that bmux is required and exactly how to get it. Used for both the daemon's socket error message and the CLI's direct failures so the wording is identical wherever the user hits it.

func BmuxSocketPathForSession

func BmuxSocketPathForSession(paths Paths, sessionName string) (string, error)

func BmuxTokenFileForSession

func BmuxTokenFileForSession(paths Paths, sessionName string) (string, error)

func BotletsBootstrapPresent

func BotletsBootstrapPresent(brainRoot string) (bool, error)

BotletsBootstrapPresent reports whether `BOOTSTRAP.md` exists as a regular file at the top of the given brain root. Symlinks do not count as present; the file must be a real on-disk regular file the agent owns.

func BotletsBrainRootForProfile

func BotletsBrainRootForProfile(paths Paths, state ProfileState, profile string) (string, bool)

BotletsBrainRootForProfile returns the validated local brain projection for the first registered Botlets bot owned by profile. Callers use this as a best-effort working directory hint; if local sync is missing or stale, the caller should keep its existing fallback so setup/recovery prompts can still run.

func BuildBotletsSetupOrientation

func BuildBotletsSetupOrientation(input BotletsSetupOrientationInput) (string, error)

BuildBotletsSetupOrientation returns the setup prompt that the daemon injects into the bot runtime on startup. The prompt either directs the agent to follow BOOTSTRAP.md or notes that bootstrap is skipped, based on whether `BOOTSTRAP.md` exists in the brain root right now.

func BuildBotletsTaskOrientation

func BuildBotletsTaskOrientation(input BotletsTaskOrientationInput) (string, error)

BuildBotletsTaskOrientation returns the local message body for a leased botlets.task notification. The body lists file names and exact paths only and never inlines brain file contents.

func ClaudeSessionFilePath

func ClaudeSessionFilePath(userHome string, cwd string, runtimeSessionRef string) (string, error)

func CloudMessageFromLease

func CloudMessageFromLease(messageID string, profile string, bot BotRegistryEntry, profileConfig AgentProfile, lease CloudNotificationLease, now time.Time) (CloudNotificationMessage, PrivateCloudMessageMetadata, error)

func CompleteCloudNotificationClaimOperation

func CompleteCloudNotificationClaimOperation(paths Paths, op CloudNotificationClaimOperation, now time.Time) error

func CompleteCloudNotificationWaitOperation

func CompleteCloudNotificationWaitOperation(paths Paths, op CloudNotificationWaitOperation, now time.Time) error

func DaemonAuthPath

func DaemonAuthPath(paths Paths) string

DaemonAuthPath returns the location of the daemon pairing credential file.

func DefaultBaseURL

func DefaultBaseURL() string

DefaultBaseURL returns the canonical Comment.io base URL after applying the COMMENT_IO_BASE_URL override. Callers outside the daemon (e.g. CLI subcommands that need a base URL without loading any profile) should use this rather than hardcoding the value.

func DefaultHomeDir

func DefaultHomeDir() (string, error)

func DefaultTmuxPollInterval

func DefaultTmuxPollInterval() time.Duration

func DefaultTmuxSubmitDelay

func DefaultTmuxSubmitDelay() time.Duration

func DeleteAutoUpdateJournal

func DeleteAutoUpdateJournal(paths Paths) error

DeleteAutoUpdateJournal removes the journal. A missing file is not an error.

func DeleteDaemonAuth

func DeleteDaemonAuth(paths Paths) error

DeleteDaemonAuth removes the daemon pairing credential. A missing file is not an error.

func EnsureBaseDirs

func EnsureBaseDirs(paths Paths) error

func ExecManagedSession

func ExecManagedSession(paths Paths, sessionID string, generation string) error

func ExpandHome

func ExpandHome(path string) (string, error)

ExpandHome resolves a path that may start with `~` (the current user's home) or be relative to the current working directory. Returns a clean absolute path.

func FindPrivateCloudMessageByNotificationID

func FindPrivateCloudMessageByNotificationID(paths Paths, profile string, notificationID string) (string, bool, error)

func GenerateLocalID

func GenerateLocalID(kind string, length int) (string, error)

func GenerateSocketRequestID

func GenerateSocketRequestID() (string, error)

func GenerateUUIDv4

func GenerateUUIDv4() (string, error)

func HasFatalProfileReloadError

func HasFatalProfileReloadError(errs []ProfileReloadError) bool

HasFatalProfileReloadError reports whether the errors include any failure that should prevent applying a newly loaded ProfileState. Per-agent-profile entry errors (a single bad profile file) are not fatal — they are reported but should let the other valid profiles load. Directory-level errors and bot-registry errors remain fatal (preserve previous state) to avoid dropping working bots on a transient registry edit.

func HasPendingCloudNotificationClaimOperationForMessage

func HasPendingCloudNotificationClaimOperationForMessage(paths Paths, profile string, messageID string, claimID string, notificationID string) (bool, error)

func HasPendingTerminalCloudNotificationClaimOperation

func HasPendingTerminalCloudNotificationClaimOperation(ops []CloudNotificationClaimOperation) bool

func HasPendingTerminalCloudNotificationClaimOperationForMessage

func HasPendingTerminalCloudNotificationClaimOperationForMessage(paths Paths, profile string, messageID string, claimID string, notificationID string) (bool, error)

func IsTmuxWrapperScript

func IsTmuxWrapperScript(path string) bool

IsTmuxWrapperScript is the exported form of looksLikeWrapperScript for diagnostics.

func LoadProfileState

func LoadProfileState(ctx context.Context, options ProfileLoadOptions) (ProfileState, []ProfileReloadError)

func LocalSyncOrientationPaths

func LocalSyncOrientationPaths(paths Paths) (docsRoot string, syncRoot string)

LocalSyncOrientationPaths returns the local docs/sync roots used in startup orientation. CLI setup code uses this to build the same preview prompt the daemon will paste into managed runtimes.

func NormalizeAgentModel

func NormalizeAgentModel(value string) (string, bool)

func NormalizeBotDisplayNameForRegistry

func NormalizeBotDisplayNameForRegistry(displayName string) string

NormalizeBotDisplayNameForRegistry returns a registry-safe display name or an empty string when the input cannot be safely represented.

func NormalizeNotificationKind

func NormalizeNotificationKind(value string) (string, bool)

func NormalizeNotificationRefs

func NormalizeNotificationRefs(notification CloudNotification) map[string]any

func OpenPrivateFile

func OpenPrivateFile(root string, path string, label string) (*os.File, error)

func ReadCapability

func ReadCapability(path string) (string, error)

func ReadCapabilityFromReader

func ReadCapabilityFromReader(reader io.Reader) (string, error)

func ReadPrivateCapability

func ReadPrivateCapability(root string, path string, label string) (string, error)

func RecordCloudNotificationClaimOperationFailure

func RecordCloudNotificationClaimOperationFailure(paths Paths, op CloudNotificationClaimOperation, failure error, now time.Time) error

func RemoveMessageSpool

func RemoveMessageSpool(paths Paths, profile string, messageID string) error

func ResolveBotletsBrainProjectionHint

func ResolveBotletsBrainProjectionHint(paths Paths, bot BotRegistryEntry) (string, error)

ResolveBotletsBrainProjectionHint returns the expected brain projection root for startup orientation. It keeps the registry and placement checks, but allows the final on-disk brain directory to be missing so the runtime can explain the sync/setup problem to the bot instead of falling back to a generic Comment.io prompt.

func ResolveBotletsHome

func ResolveBotletsHome(home string) (string, error)

func ResolveConfiguredBmuxBinary

func ResolveConfiguredBmuxBinary(explicit string) string

func ResolveConfiguredTmuxBinary

func ResolveConfiguredTmuxBinary(explicit string) string

ResolveConfiguredTmuxBinary picks the tmux binary name/path to use. An explicit value (e.g. the --tmux-bin flag) wins; otherwise COMMENT_IO_TMUX_BIN; otherwise the bare name "tmux". The bare name is resolved by command() against a fixed set of trusted directories — never the caller's $PATH — so a wrapper shim dropped on PATH cannot shadow the real binary.

func ResolveDaemonTmuxBinary

func ResolveDaemonTmuxBinary(explicit string) (string, error)

ResolveDaemonTmuxBinary resolves the absolute tmux path the daemon would use, applying the same configured-binary + trusted-directory rules (never $PATH). Used by diagnostics (comment doctor) so they report the exact binary the daemon runs.

func RunSessionExec

func RunSessionExec(options SessionExecOptions) error

func RuntimeEnvironmentVars

func RuntimeEnvironmentVars() []string

RuntimeEnvironmentVars returns the environment entries ("KEY=VALUE") that a daemon-launched managed runtime needs so it resolves the same environment as the daemon that launched it. The managed-session and tmux launch paths scrub the environment down to a safe allowlist, which would otherwise strip COMMENT_IO_ENV and let a staging daemon spawn production runtimes (e.g. the runtime's own `comment sync login` would write to the production ~/Comment Docs root). It is derived from the active, daemon-resolved environment — never from a runtime-supplied value — and returns nil for production so production runtimes keep their environment unchanged.

func SaveDaemonAuth

func SaveDaemonAuth(paths Paths, auth DaemonAuth) error

SaveDaemonAuth persists the daemon pairing credential with owner-only permissions (0600) under a trust-validated 0700 bus directory, creating the home and bus directories with the same trust conventions the agent-profile writer uses.

func SetCLIReleaseVersion

func SetCLIReleaseVersion(v string)

SetCLIReleaseVersion records the running CLI's build version so bmux channel selection can detect a prerelease (staging) build. Call once from the command entry point before any bmux install runs: it is a process-global set at startup, not a per-request value.

func StagingServiceBaseURLOverride

func StagingServiceBaseURLOverride() string

StagingServiceBaseURLOverride returns the explicit base-URL override to bake into a staging daemon's service definition, or "" when none is set or this is production. The installed daemon does not inherit the installing shell's environment, so without this an override-configured staging install would silently fall back to the compiled-in staging default after a restart. Production service files are intentionally left untouched.

func TmuxInstallHint

func TmuxInstallHint() string

TmuxInstallHint returns a short, platform-appropriate instruction for installing tmux. It is robust across macOS and the common Linux distribution families, with a package-manager-agnostic fallback when the distro can't be identified.

func TmuxInstallHintShort

func TmuxInstallHintShort() string

TmuxInstallHintShort returns just the install command for the current platform as a single line (e.g. "brew install tmux", "sudo apt install tmux"), suitable for compact contexts like `comment doctor` JSON output where the full multi-line guidance from TmuxInstallHint would be unwieldy.

func TmuxNotInstalledMessage

func TmuxNotInstalledMessage() string

TmuxNotInstalledMessage builds the full, human-readable error explaining that tmux is required and exactly how to install it on the current platform. Used for both the daemon's socket error message and the CLI's direct failures so the wording is identical wherever the user hits it.

func TmuxPaneTargetSession

func TmuxPaneTargetSession(paneTarget string) (string, bool)

TmuxPaneTargetSession extracts the session name from a session:window.pane target.

func TmuxSocketNameForPaneTarget

func TmuxSocketNameForPaneTarget(paneTarget string) (string, error)

TmuxSocketNameForPaneTarget derives the isolated tmux socket from a session-qualified pane target. Raw %pane targets are legacy/default-server references and intentionally return an empty socket name.

func TmuxSocketNameForSession

func TmuxSocketNameForSession(sessionName string) (string, error)

TmuxSocketNameForSession returns the per-session tmux socket used to isolate managed runtimes from the default shared tmux server.

func TrustedBmuxBinaryPath

func TrustedBmuxBinaryPath(explicit string) (string, error)

func UpdateMessageSpoolNudge

func UpdateMessageSpoolNudge(paths Paths, record SessionRecord, message MessageEnvelope) error

func ValidateAgentProfileWriteTarget

func ValidateAgentProfileWriteTarget(paths Paths, handle string) (string, error)

func ValidateBotletsBrainProjection

func ValidateBotletsBrainProjection(paths Paths, bot BotRegistryEntry) (string, error)

func ValidateBotletsRegistryWriteTarget

func ValidateBotletsRegistryWriteTarget(home string) (string, error)

func ValidateLocalID

func ValidateLocalID(kind, value string) error

func ValidateSocketRequest

func ValidateSocketRequest(req SocketRequest) error

func WaitForSessionFileGrowth

func WaitForSessionFileGrowth(ctx context.Context, before SessionFileSnapshot, timeout time.Duration, poll time.Duration) (bool, error)

func WriteAutoUpdateJournal

func WriteAutoUpdateJournal(paths Paths, journal AutoUpdateJournal) error

WriteAutoUpdateJournal persists the rollback journal atomically.

func WriteAutoUpdateState

func WriteAutoUpdateState(paths Paths, state AutoUpdateState) error

WriteAutoUpdateState persists the cached health state atomically.

func WriteBusConfig

func WriteBusConfig(paths Paths, config BusConfig) error

func WriteMessageSpool

func WriteMessageSpool(paths Paths, message MessageEnvelope) error

func WriteNewSessionRecord

func WriteNewSessionRecord(paths Paths, record SessionRecord) error

func WritePrivateCloudMessageMetadata

func WritePrivateCloudMessageMetadata(paths Paths, metadata PrivateCloudMessageMetadata) error

func WritePrivateFileAtomic

func WritePrivateFileAtomic(path string, data []byte, mode os.FileMode) error

func WritePrivateFileAtomicExistingDir

func WritePrivateFileAtomicExistingDir(path string, data []byte, mode os.FileMode) error

func WritePrivateFileAtomicNoReplace

func WritePrivateFileAtomicNoReplace(path string, data []byte, mode os.FileMode) error

func WriteSessionRecord

func WriteSessionRecord(paths Paths, record SessionRecord) error

Types

type AgentProfile

type AgentProfile struct {
	Handle      string
	AgentSecret string
	BaseURL     string
	Runtime     string
	Model       string
	Path        string
}

type AgentProfileWrite

type AgentProfileWrite struct {
	Path    string
	Data    []byte
	Profile AgentProfile
}

AgentProfileWrite is a prepared, validated agent profile write: the target path under the agents directory, the serialized JSON payload, and the in-memory AgentProfile the payload represents.

func PrepareAgentProfileWrite

func PrepareAgentProfileWrite(paths Paths, handle, agentSecret, baseURL, runtime string) (AgentProfileWrite, error)

PrepareAgentProfileWrite validates the agent credential and target path and builds the profile JSON without touching the filesystem beyond trust checks on the agents directory. Call Write on the result to persist it.

func PrepareAgentProfileWriteWithModel

func PrepareAgentProfileWriteWithModel(paths Paths, handle, agentSecret, baseURL, runtime, model string) (AgentProfileWrite, error)

func (AgentProfileWrite) Write

func (w AgentProfileWrite) Write() error

Write atomically persists the prepared profile with owner-only permissions.

type AutoUpdateJournal

type AutoUpdateJournal struct {
	FromVersion string    `json:"from_version"`
	ToVersion   string    `json:"to_version"`
	PackageName string    `json:"package_name"`
	Attempts    int       `json:"attempts"`
	StartedAt   time.Time `json:"started_at"`
}

AutoUpdateJournal is the rollback journal written before an auto-upgrade acts.

func ReadAutoUpdateJournal

func ReadAutoUpdateJournal(paths Paths) (AutoUpdateJournal, bool)

ReadAutoUpdateJournal returns the pending-update journal and whether it exists. A missing file reports (zero, false); an unreadable/corrupt file is treated the same so a garbage journal never wedges startup — it is simply re-evaluated as "no pending update".

type AutoUpdateState

type AutoUpdateState struct {
	LatestVersion    string `json:"latest_version,omitempty"`
	UpdateAvailable  bool   `json:"update_available"`
	LastUpdateAt     string `json:"last_update_at,omitempty"`
	LastUpdateResult string `json:"last_update_result,omitempty"`
	// LastRolledBackVersion is the toVersion the daemon last rolled back FROM.
	// While LastUpdateResult is rolled_back and this matches the fetched target,
	// the worker skips re-upgrading to the same known-bad release (anti-thrash).
	LastRolledBackVersion string `json:"last_rolled_back_version,omitempty"`
}

AutoUpdateState is the cached health view of the auto-updater.

func ReadAutoUpdateState

func ReadAutoUpdateState(paths Paths) AutoUpdateState

ReadAutoUpdateState returns the cached health state. A missing or corrupt file reports the zero value (treated as "none").

type BmuxInstallOptions

type BmuxInstallOptions struct {
	// BaseURL overrides the release-asset base entirely (mirrors / air-gapped /
	// tests). When set, assets are fetched from "<BaseURL>/<asset>". Falls back to
	// the BMUX_BASE_URL environment variable, then to the public GitHub release.
	BaseURL string
	// InstallDir is the directory the binary is written to. Defaults to
	// BMUX_INSTALL_DIR, then ~/.local/bin (a trusted search dir).
	InstallDir string
	// Version is the release tag to install ("latest" by default, or BMUX_VERSION).
	Version string
	// Force reinstalls even when a usable bmux already resolves.
	Force bool
}

BmuxInstallOptions controls EnsureBmuxInstalled. The zero value installs the latest published bmux for the current platform into the default trusted directory (~/.local/bin) from the public GitHub release.

type BmuxInstallResult

type BmuxInstallResult struct {
	// Path is the absolute path to the usable bmux binary.
	Path string
	// Installed is true when this call downloaded and placed the binary.
	Installed bool
	// AlreadyPresent is true when a usable bmux already resolved and the install
	// was skipped (Force was false).
	AlreadyPresent bool
	// Discoverable is true when an unpinned launchd/systemd daemon — which
	// resolves bare "bmux" from the trusted directories and does NOT inherit the
	// shell's COMMENT_IO_BMUX_BIN — can find a bmux. When false, the caller must
	// pin COMMENT_IO_BMUX_BIN to Path (or the daemon will still fail to launch).
	Discoverable bool
}

BmuxInstallResult reports what EnsureBmuxInstalled did.

func EnsureBmuxInstalled

func EnsureBmuxInstalled(opts BmuxInstallOptions) (BmuxInstallResult, error)

EnsureBmuxInstalled makes the bmux binary available in a trusted directory so the daemon can launch managed runtimes. If bmux already resolves it is a no-op (unless opts.Force). Otherwise it downloads the prebuilt binary for the current platform from the public release, verifies its checksum when SHA256SUMS is published, and installs it to ~/.local/bin/bmux. It returns ErrBmuxNotInstalled (wrapped) when the current platform has no prebuilt binary.

type BotBrainRef

type BotBrainRef struct {
	WorkspaceID     string `json:"workspace_id"`
	OwnerAgentID    string `json:"owner_agent_id,omitempty"`
	BotAgentID      string `json:"bot_agent_id,omitempty"`
	ContainerID     string `json:"container_id"`
	RootFolderID    string `json:"root_folder_id"`
	RelativePath    string `json:"relative_path"`
	SetupGeneration int    `json:"setup_generation,omitempty"`
}

type BotRegistryEntry

type BotRegistryEntry struct {
	Name              string                `json:"name"`
	DisplayName       string                `json:"display_name,omitempty"`
	BotID             string                `json:"bot_id,omitempty"`
	Handle            string                `json:"handle"`
	SlugAliases       []string              `json:"slug_aliases,omitempty"`
	HandleAliases     []string              `json:"handle_aliases,omitempty"`
	CredentialProfile string                `json:"credential_profile"`
	CredentialPath    string                `json:"-"`
	RegistryRuntime   string                `json:"-"`
	BrainRef          *BotBrainRef          `json:"brain_ref,omitempty"`
	ManagedSession    ManagedSessionSetting `json:"managed_session"`
	// RespondsToMentions mirrors the bot's "Responds to @mentions" opt-in from
	// the server (the owned-agents manifest / enrollment hint). When true, the
	// daemon auto-launches the bot's runtime on an incoming doc @mention if no
	// session is already running — the same launch the web "Start your agent"
	// button triggers. omitempty so existing registry files stay byte-stable
	// until the flag is set.
	RespondsToMentions bool `json:"responds_to_mentions,omitempty"`
}

func (BotRegistryEntry) MatchesDaemonSelector

func (bot BotRegistryEntry) MatchesDaemonSelector(selector string) bool

MatchesDaemonSelector is for daemon-scoped ownership checks. It intentionally avoids the bare handle suffix convenience accepted by `comment run <bot>` because multiple bots can share the same credential profile.

func (BotRegistryEntry) MatchesProfile

func (bot BotRegistryEntry) MatchesProfile(profile string) bool

func (BotRegistryEntry) MatchesSelector

func (bot BotRegistryEntry) MatchesSelector(selector string) bool

MatchesSelector reports whether selector names this bot by current slug, current handle, an alias slug/handle, or the handle suffix used by `comment run <bot>`.

func (BotRegistryEntry) MatchesSlug

func (bot BotRegistryEntry) MatchesSlug(slug string) bool

func (BotRegistryEntry) MatchesStableIdentity

func (bot BotRegistryEntry) MatchesStableIdentity(botID string, botAgentID string) bool

func (BotRegistryEntry) StableBotAgentID

func (bot BotRegistryEntry) StableBotAgentID() string

type BotletsRepairHint

type BotletsRepairHint struct {
	Code             string `json:"code"`
	CanContinue      bool   `json:"can_continue"`
	CanonicalProfile string `json:"canonical_profile,omitempty"`
	CanonicalBotName string `json:"canonical_bot_name,omitempty"`
	SuggestedPath    string `json:"suggested_path,omitempty"`
}

func ValidateBotletsBrainProjectionWithRepairHints

func ValidateBotletsBrainProjectionWithRepairHints(paths Paths, bot BotRegistryEntry) (string, []BotletsRepairHint, error)

type BotletsSetupOrientationInput

type BotletsSetupOrientationInput struct {
	BotName             string
	BotDisplayName      string
	BotHandle           string
	BrainRoot           string
	BaseURL             string
	DocsRoot            string
	HasBootstrap        bool
	BootstrapProbeError string
}

BotletsSetupOrientationInput is the data needed to build the setup orientation prompt that the managed runtime injects after `comment botlets setup` finishes.

type BotletsTaskOrientationInput

type BotletsTaskOrientationInput struct {
	Kind         string
	BotName      string
	BotHandle    string
	RunID        string
	ScheduledFor string
	Cron         string
	Timezone     string
	BrainRoot    string
}

BotletsTaskOrientationInput is the data needed to expand a scheduled or manual botlets.task message body. All fields are required.

type BusConfig

type BusConfig struct {
	Version     int    `json:"version"`
	BotletsHome string `json:"botlets_home,omitempty"`
}

func ReadBusConfig

func ReadBusConfig(paths Paths) (BusConfig, bool, error)

type CapabilityFile

type CapabilityFile struct {
	Path    string `json:"path"`
	Created bool   `json:"created"`
}

func EnsureOwnerCapability

func EnsureOwnerCapability(paths Paths) (CapabilityFile, error)

type CloudBotletsTaskNotification

type CloudBotletsTaskNotification struct {
	RunID               string `json:"run_id"`
	Kind                string `json:"kind"`
	OwnerAgentID        string `json:"owner_agent_id"`
	BotID               string `json:"bot_id,omitempty"`
	BotAgentID          string `json:"bot_agent_id"`
	BotSlug             string `json:"bot_slug"`
	BotName             string `json:"bot_name"`
	BotHandle           string `json:"bot_handle"`
	ScheduledFor        string `json:"scheduled_for"`
	EnqueuedAt          string `json:"enqueued_at"`
	ScheduleVersion     int    `json:"schedule_version"`
	ExecutionGeneration int    `json:"execution_generation"`
	SetupGeneration     int    `json:"setup_generation"`
	Cron                string `json:"cron"`
	Timezone            string `json:"timezone"`
}

type CloudNotification

type CloudNotification struct {
	ID           string                        `json:"id"`
	Type         string                        `json:"type"`
	DocSlug      string                        `json:"doc_slug"`
	DocTitle     string                        `json:"doc_title"`
	CommentID    *string                       `json:"comment_id"`
	SuggestionID *string                       `json:"suggestion_id"`
	FromHandle   string                        `json:"from_handle"`
	FromName     string                        `json:"from_name"`
	Context      string                        `json:"context"`
	CreatedAt    string                        `json:"created_at"`
	AccessToken  string                        `json:"access_token,omitempty"`
	BotletsTask  *CloudBotletsTaskNotification `json:"botlets_task,omitempty"`
}

type CloudNotificationClaimMutation

type CloudNotificationClaimMutation struct {
	OK             bool   `json:"ok"`
	ClaimID        string `json:"claim_id"`
	NotificationID string `json:"notification_id"`
	Idempotent     bool   `json:"idempotent,omitempty"`
}

type CloudNotificationClaimOperation

type CloudNotificationClaimOperation struct {
	OpID               string `json:"op_id"`
	IdempotencyKey     string `json:"idempotency_key"`
	Operation          string `json:"operation"`
	Profile            string `json:"profile"`
	CredentialProfile  string `json:"credential_profile,omitempty"`
	LocalMessageID     string `json:"local_message_id"`
	PrivateMetadataRef string `json:"private_metadata_ref"`
	ClaimID            string `json:"claim_id"`
	NotificationID     string `json:"notification_id"`
	ClaimHolder        string `json:"claim_holder,omitempty"`
	LeaseTTLMS         int64  `json:"lease_ttl_ms,omitempty"`
	ReleaseReason      string `json:"release_reason,omitempty"`
	CreatedAt          string `json:"created_at"`
	UpdatedAt          string `json:"updated_at"`
	State              string `json:"state"`
	Attempts           int    `json:"attempts"`
	LastError          string `json:"last_error,omitempty"`
}

func BeginCloudNotificationClaimOperation

func BeginCloudNotificationClaimOperation(paths Paths, operation string, profile string, messageID string, claimID string, notificationID string, requestedOpID string, leaseTTL time.Duration, now time.Time, releaseReason ...string) (CloudNotificationClaimOperation, bool, error)

func BeginCloudNotificationClaimOperationForHolder

func BeginCloudNotificationClaimOperationForHolder(paths Paths, operation string, profile string, messageID string, claimID string, notificationID string, claimHolder string, requestedOpID string, leaseTTL time.Duration, now time.Time, releaseReason ...string) (CloudNotificationClaimOperation, bool, error)

func BeginDeclinedDuplicateCloudNotificationReleaseOperation

func BeginDeclinedDuplicateCloudNotificationReleaseOperation(paths Paths, profile string, messageID string, claimID string, notificationID string, now time.Time) (CloudNotificationClaimOperation, bool, error)

func EnsureCloudNotificationClaimOperationCredentialProfile

func EnsureCloudNotificationClaimOperationCredentialProfile(paths Paths, op CloudNotificationClaimOperation, credentialProfile string, now time.Time) (CloudNotificationClaimOperation, error)

func FindDoneCloudNotificationClaimOperation

func FindDoneCloudNotificationClaimOperation(paths Paths, operation string, profile string, messageID string, claimID string, notificationID string, leaseTTL time.Duration) (CloudNotificationClaimOperation, bool, error)

func FindPendingCloudNotificationClaimOperation

func FindPendingCloudNotificationClaimOperation(paths Paths, operation string, profile string, messageID string, claimID string, notificationID string, leaseTTL time.Duration) (CloudNotificationClaimOperation, bool, error)

func ListPendingCloudNotificationClaimOperations

func ListPendingCloudNotificationClaimOperations(paths Paths) ([]CloudNotificationClaimOperation, error)

func ListPendingCloudNotificationClaimOperationsForLocalMessage

func ListPendingCloudNotificationClaimOperationsForLocalMessage(paths Paths, profile string, messageID string) ([]CloudNotificationClaimOperation, error)

func ListPendingCloudNotificationClaimOperationsForMessage

func ListPendingCloudNotificationClaimOperationsForMessage(paths Paths, profile string, messageID string, claimID string, notificationID string) ([]CloudNotificationClaimOperation, error)

func ReadDoneCloudNotificationClaimOperation

func ReadDoneCloudNotificationClaimOperation(paths Paths, opID string) (CloudNotificationClaimOperation, bool, error)

func ReadPendingCloudNotificationClaimOperation

func ReadPendingCloudNotificationClaimOperation(paths Paths, opID string) (CloudNotificationClaimOperation, bool, error)

func ReadPendingCloudNotificationClaimOperationWithRetry

func ReadPendingCloudNotificationClaimOperationWithRetry(paths Paths, opID string) (CloudNotificationClaimOperation, bool, error)

type CloudNotificationHandlingRequest

type CloudNotificationHandlingRequest struct {
	Action          string `json:"action"`
	TTLMS           int64  `json:"ttl_ms,omitempty"`
	Outcome         string `json:"outcome,omitempty"`
	ClaimGeneration string `json:"claim_generation,omitempty"`
	ProgressAt      string `json:"progress_at,omitempty"`
}

type CloudNotificationHandlingResult

type CloudNotificationHandlingResult struct {
	OK              bool           `json:"ok"`
	Activity        map[string]any `json:"activity"`
	Ignored         bool           `json:"ignored,omitempty"`
	TerminalOutcome string         `json:"terminal_outcome,omitempty"`
	Idempotent      bool           `json:"idempotent,omitempty"`
}

type CloudNotificationLease

type CloudNotificationLease struct {
	ClaimID        string            `json:"claim_id"`
	NotificationID string            `json:"notification_id"`
	ClaimedAt      string            `json:"claimed_at"`
	LeaseExpiresAt string            `json:"lease_expires_at"`
	Notification   CloudNotification `json:"notification"`
}

type CloudNotificationMessage

type CloudNotificationMessage struct {
	ID             string
	Profile        string
	BotName        string
	BotID          string
	BotAgentID     string
	Kind           string
	From           string
	Body           MessageBody
	Refs           map[string]any
	ThreadID       *string
	NotificationID string
	CreatedAt      string
	LeaseExpiresAt string
	Now            time.Time
}

func ExpandBotletsTaskMessageBody

func ExpandBotletsTaskMessageBody(paths Paths, bot BotRegistryEntry, message CloudNotificationMessage, notification CloudNotification) (CloudNotificationMessage, error)

ExpandBotletsTaskMessageBody replaces the markdown body of a leased botlets.task message with the Botlets-oriented task prompt. Returns the message unchanged for any other kind, when the notification has no botlets_task payload, or when brain projection validation fails. The caller is responsible for logging the returned error if present.

type CloudNotificationWaitOperation

type CloudNotificationWaitOperation struct {
	OpID        string   `json:"op_id"`
	Kind        string   `json:"kind"`
	Profile     string   `json:"profile"`
	BotName     string   `json:"bot_name,omitempty"`
	Kinds       []string `json:"kinds,omitempty"`
	TimeoutMS   int64    `json:"timeout_ms"`
	LeaseTTLMS  int64    `json:"lease_ttl_ms"`
	LeaseHolder string   `json:"lease_holder"`
	Attempts    int      `json:"attempts"`
	CreatedAt   string   `json:"created_at"`
	LastAttempt string   `json:"last_attempt_at"`
}

func BeginCloudNotificationWaitOperation

func BeginCloudNotificationWaitOperation(paths Paths, profile string, timeout time.Duration, leaseTTL time.Duration, leaseHolder string, now time.Time) (CloudNotificationWaitOperation, error)

func BeginCloudNotificationWaitOperationForBot

func BeginCloudNotificationWaitOperationForBot(paths Paths, profile string, botName string, timeout time.Duration, leaseTTL time.Duration, leaseHolder string, now time.Time) (CloudNotificationWaitOperation, error)

func BeginCloudNotificationWaitOperationForBotAndKinds

func BeginCloudNotificationWaitOperationForBotAndKinds(paths Paths, profile string, botName string, timeout time.Duration, leaseTTL time.Duration, leaseHolder string, kinds []string, now time.Time) (CloudNotificationWaitOperation, error)

func ReadDoneCloudNotificationWaitOperation

func ReadDoneCloudNotificationWaitOperation(paths Paths, opID string) (CloudNotificationWaitOperation, bool, error)

func ReadPendingCloudNotificationWaitOperation

func ReadPendingCloudNotificationWaitOperation(paths Paths, profile string) (CloudNotificationWaitOperation, bool, error)

func ReadPendingCloudNotificationWaitOperationForBot

func ReadPendingCloudNotificationWaitOperationForBot(paths Paths, profile string, botName string) (CloudNotificationWaitOperation, bool, error)

func ReadPendingCloudNotificationWaitOperationForBotAndKinds

func ReadPendingCloudNotificationWaitOperationForBotAndKinds(paths Paths, profile string, botName string, kinds []string) (CloudNotificationWaitOperation, bool, error)

func ReadPendingCloudNotificationWaitOperationForBotAndKindsWithRetry

func ReadPendingCloudNotificationWaitOperationForBotAndKindsWithRetry(paths Paths, profile string, botName string, kinds []string) (CloudNotificationWaitOperation, bool, error)

func ReadPendingCloudNotificationWaitOperationForBotWithRetry

func ReadPendingCloudNotificationWaitOperationForBotWithRetry(paths Paths, profile string, botName string) (CloudNotificationWaitOperation, bool, error)

func ReadPendingCloudNotificationWaitOperationWithRetry

func ReadPendingCloudNotificationWaitOperationWithRetry(paths Paths, profile string) (CloudNotificationWaitOperation, bool, error)

func RecordCloudNotificationWaitOperationAttempt

func RecordCloudNotificationWaitOperationAttempt(paths Paths, op CloudNotificationWaitOperation, now time.Time) (CloudNotificationWaitOperation, error)

type CloudNotificationWake

type CloudNotificationWake struct {
	Type                 string `json:"type"`
	WakeID               string `json:"wake_id"`
	UnreadCount          int    `json:"unread_count"`
	NewestNotificationID string `json:"newest_notification_id,omitempty"`
}

type CodexRolloutMatch

type CodexRolloutMatch struct {
	Path      string
	SessionID string
	ModTime   time.Time
}

func FindCodexRolloutCorrelation

func FindCodexRolloutCorrelation(userHome string, cwd string, nonce string, now time.Time) (CodexRolloutMatch, bool, error)

func WaitForCodexRolloutCorrelation

func WaitForCodexRolloutCorrelation(ctx context.Context, userHome string, cwd string, nonce string, timeout time.Duration, poll time.Duration) (CodexRolloutMatch, bool, error)

type Daemon

type Daemon struct {
	// contains filtered or unexported fields
}

func StartDaemon

func StartDaemon(ctx context.Context, options DaemonOptions) (*Daemon, error)

func (*Daemon) Close

func (d *Daemon) Close() error

func (*Daemon) HandleRequest

func (d *Daemon) HandleRequest(ctx context.Context, conn net.Conn, raw []byte) SocketResponse

func (*Daemon) TCPAddr

func (d *Daemon) TCPAddr() string

TCPAddr returns the resolved address of the opt-in bus TCP listener, or "" if no TCP listener is configured. Useful when the configured address used a :0 port and the real port must be discovered.

type DaemonAuth

type DaemonAuth struct {
	DaemonID     string   `json:"daemon_id"`
	Token        string   `json:"daemon_token"`
	BaseURL      string   `json:"base_url"`
	Label        string   `json:"label"`
	Capabilities []string `json:"capabilities"`
	PairedAt     string   `json:"paired_at"`
}

DaemonAuth is this computer's paired-daemon credential, written by `comment bus pair` and read by the daemon and CLI. The Token is secret-equivalent: it must never be logged, printed, or included in diagnostics.

func LoadDaemonAuth

func LoadDaemonAuth(paths Paths) (DaemonAuth, bool, error)

LoadDaemonAuth reads the daemon pairing credential. A missing file is not an error: it returns the zero value with ok=false. A present-but-unusable file (symlink, unparseable, missing required fields) returns an error so callers can tell "unpaired" apart from "broken".

type DaemonOptions

type DaemonOptions struct {
	Paths                     Paths
	Version                   string
	BotletsHome               string
	DefaultBaseURL            string
	ExpectedUID               *uint32
	PeerCredentialFunc        func(*net.UnixConn) (PeerCredential, error)
	NotificationClient        NotificationClient
	EnableNotificationPollers bool
	Tmux                      TmuxController
	TmuxBinary                string
	Bmux                      TmuxController
	BmuxBinary                string
	TmuxPollInterval          time.Duration
	TmuxSubmitDelay           time.Duration
	CommentExecutable         func() (string, error)
	LogWriter                 io.Writer
	Now                       func() time.Time
	// AllowNonLoopbackTCP permits binding the opt-in bus TCP listener
	// (Paths.BusTCPAddr) to a non-loopback address. Default false: a non-loopback
	// bind is refused so the cap-token-only TCP path can't be accidentally exposed
	// to a LAN. Containers that must bind 0.0.0.0 for Docker port publishing set
	// this explicitly and publish the port to host loopback only.
	AllowNonLoopbackTCP bool
	// TCPListenAddr, when non-empty, is the address the daemon binds its opt-in
	// TCP control listener to (e.g. "0.0.0.0:7700" inside a container). This is
	// the DAEMON's bind address, deliberately separate from Paths.BusTCPAddr (the
	// CLIENT's dial address) so a host that exports the dial address to reach a
	// caged daemon doesn't make a native `comment bus run` try to bind it.
	TCPListenAddr string
	// DisableUnixListener skips the Unix-socket listener entirely (TCP-only).
	// Requires TCPListenAddr to be set. Used in containers whose state dir is a
	// bind mount that can't chmod a socket file (macOS virtiofs).
	DisableUnixListener bool
	// MentionAutoStart, when non-nil, launches an already-installed agent's
	// runtime detached (the same path the web "Start your agent" button uses)
	// when a doc @mention arrives for a bot whose "Responds to @mentions" flag is
	// on and nothing is running. nil disables mention auto-launch. It lives in
	// package main (launchAgentRuntimeDetached), so it is injected here rather
	// than imported by package commentbus.
	MentionAutoStart func(ctx context.Context, paths Paths, handle string) error
	// SocketWatchdogInterval is how often the running daemon re-checks that its
	// Unix listening socket still exists and is the file it bound, re-binding it
	// if it vanished. 0 uses defaultSocketWatchdogInterval; a negative value
	// disables the watchdog (tests use this to demonstrate the pre-fix wedge); a
	// positive value sets a fast interval to exercise recovery. Ignored for a
	// TCP-only daemon (no Unix socket to watch).
	SocketWatchdogInterval time.Duration
	// StartupInitTimeout bounds the store/capability init that runs after the
	// singleton lock is acquired but before the socket is bound. <=0 uses
	// defaultStartupInitTimeout. On timeout StartDaemon fails and releases the
	// lock rather than wedging alive-but-socketless.
	StartupInitTimeout time.Duration
	// SocketRebindTimeout bounds a watchdog re-bind (net.ListenUnix/chmod/lstat,
	// none context-cancellable). <=0 uses defaultSocketRebindTimeout. It caps how
	// long Close can wait to join a watchdog stuck re-binding on a wedged
	// filesystem. Tests set a small value.
	SocketRebindTimeout time.Duration
}

type DailyResetRecord

type DailyResetRecord struct {
	Date                 string `json:"date"`
	State                string `json:"state"`
	Reason               string `json:"reason"`
	RequestedAt          string `json:"requested_at"`
	PromptedAt           string `json:"prompted_at,omitempty"`
	DeadlineAt           string `json:"deadline_at"`
	LogPath              string `json:"log_path"`
	CompletedAt          string `json:"completed_at,omitempty"`
	ReplacementSessionID string `json:"replacement_session_id,omitempty"`
}

type Environment

type Environment struct {
	Name string
}

Environment identifies which Comment.io deployment a CLI invocation targets.

It is resolved once at process entry (see applyEnvironment in the comment command) and published through the COMMENT_IO_ENV variable so the daemon, managed runtimes, and every default resolver in this package agree on the same environment. Production is the default; staging is opt-in and uses a fully separate on-disk root, API endpoint, and synced-docs folder so the two can coexist on one machine without clobbering each other.

func CurrentEnvironment

func CurrentEnvironment() Environment

CurrentEnvironment resolves the active environment from COMMENT_IO_ENV. Anything other than an explicit, case-insensitive "staging" is production.

func (Environment) DefaultBaseURL

func (e Environment) DefaultBaseURL() string

DefaultBaseURL returns the default API base URL for the environment, honoring the COMMENT_IO_BASE_URL (both environments) and COMMENT_IO_STAGING_BASE_URL (staging only) overrides. A profile's own base_url still takes precedence over this default when one is configured.

func (Environment) DefaultHomeDir

func (e Environment) DefaultHomeDir() (string, error)

DefaultHomeDir returns the default local state root for the environment: ~/.comment-io for production, ~/.comment-io-staging for staging. An explicit --home flag or COMMENT_IO_HOME still overrides this default.

func (Environment) DefaultSyncRootName

func (e Environment) DefaultSyncRootName() string

DefaultSyncRootName returns the user-facing local docs folder name for the environment. Staging uses a separate folder so production projections under ~/Comment Docs are never clobbered.

func (Environment) IsStaging

func (e Environment) IsStaging() bool

IsStaging reports whether this is the staging environment.

type ExecBmuxController

type ExecBmuxController struct {
	Binary string
	Paths  Paths
}

func NewExecBmuxController

func NewExecBmuxController(paths Paths, explicit string) ExecBmuxController

func (ExecBmuxController) BmuxStatus

func (c ExecBmuxController) BmuxStatus(ctx context.Context, sessionName string) (bmuxStatus, error)

func (ExecBmuxController) CapturePane

func (c ExecBmuxController) CapturePane(context.Context, string, int) (string, error)

func (ExecBmuxController) HasSession

func (c ExecBmuxController) HasSession(ctx context.Context, sessionName string) (bool, error)

func (ExecBmuxController) KillSession

func (c ExecBmuxController) KillSession(ctx context.Context, sessionName string) error

func (ExecBmuxController) NewSession

func (c ExecBmuxController) NewSession(ctx context.Context, options TmuxNewSessionOptions) error

func (ExecBmuxController) PaneBelongsToSession

func (c ExecBmuxController) PaneBelongsToSession(_ context.Context, sessionName string, paneTarget string) (bool, error)

func (ExecBmuxController) PaneCurrentCommand

func (c ExecBmuxController) PaneCurrentCommand(ctx context.Context, paneTarget string) (string, error)

func (ExecBmuxController) PaneTarget

func (c ExecBmuxController) PaneTarget(_ context.Context, sessionName string) (string, error)

func (ExecBmuxController) PasteText

func (c ExecBmuxController) PasteText(ctx context.Context, paneTarget string, text string) error

func (ExecBmuxController) SendEnter

func (c ExecBmuxController) SendEnter(ctx context.Context, paneTarget string) error

func (ExecBmuxController) SendLiteral

func (c ExecBmuxController) SendLiteral(ctx context.Context, paneTarget string, text string) error

func (ExecBmuxController) WaitForOutput

func (c ExecBmuxController) WaitForOutput(ctx context.Context, sessionName string, needle []byte, timeout time.Duration) (bool, error)

type ExecTmuxController

type ExecTmuxController struct {
	Binary string
}

func NewExecTmuxController

func NewExecTmuxController() ExecTmuxController

func (ExecTmuxController) CapturePane

func (c ExecTmuxController) CapturePane(ctx context.Context, paneTarget string, lines int) (string, error)

func (ExecTmuxController) HasSession

func (c ExecTmuxController) HasSession(ctx context.Context, sessionName string) (bool, error)

func (ExecTmuxController) KillSession

func (c ExecTmuxController) KillSession(ctx context.Context, sessionName string) error

func (ExecTmuxController) NewSession

func (c ExecTmuxController) NewSession(ctx context.Context, options TmuxNewSessionOptions) error

func (ExecTmuxController) PaneBelongsToSession

func (c ExecTmuxController) PaneBelongsToSession(ctx context.Context, sessionName string, paneTarget string) (bool, error)

func (ExecTmuxController) PaneCurrentCommand

func (c ExecTmuxController) PaneCurrentCommand(ctx context.Context, paneTarget string) (string, error)

func (ExecTmuxController) PaneTarget

func (c ExecTmuxController) PaneTarget(ctx context.Context, sessionName string) (string, error)

func (ExecTmuxController) PasteText

func (c ExecTmuxController) PasteText(ctx context.Context, paneTarget string, text string) error

PasteText loads `text` (which MAY contain newlines) into a named tmux paste buffer and pastes it into the target pane with bracketed-paste mode so the receiving program (e.g. Claude Code) treats the whole block as a single paste event rather than line-by-line submissions. The buffer is deleted after pasting. Caller is responsible for sending Enter to submit the resulting input.

Both the load and the paste go through `c.command()` so they inherit the trusted binary path, safeProcessEnv, and the tmux command timeout every other tmux operation uses. A per-call named buffer (-b NAME) isolates this paste from concurrent calls — tmux's implicit current buffer is global state and would race when two startup-orientation sends fire for different panes at the same time.

func (ExecTmuxController) SendEnter

func (c ExecTmuxController) SendEnter(ctx context.Context, paneTarget string) error

func (ExecTmuxController) SendLiteral

func (c ExecTmuxController) SendLiteral(ctx context.Context, paneTarget string, text string) error

type HTTPNotificationClient

type HTTPNotificationClient struct {
	Client *http.Client
	// contains filtered or unexported fields
}

func NewHTTPNotificationClient

func NewHTTPNotificationClient(client *http.Client) *HTTPNotificationClient

func NewVersionedHTTPNotificationClient

func NewVersionedHTTPNotificationClient(version string, client *http.Client) *HTTPNotificationClient

func (*HTTPNotificationClient) AckNotification

func (c *HTTPNotificationClient) AckNotification(ctx context.Context, profile AgentProfile, claimID string, idempotencyKey string) (*CloudNotificationClaimMutation, error)

func (*HTTPNotificationClient) LeaseNotification

func (c *HTTPNotificationClient) LeaseNotification(ctx context.Context, profile AgentProfile, leaseTTL time.Duration, leaseHolder string, idempotencyKey string, kinds ...string) (*CloudNotificationLease, error)

func (*HTTPNotificationClient) PublishNotificationHandlingActivity

func (c *HTTPNotificationClient) PublishNotificationHandlingActivity(ctx context.Context, profile AgentProfile, claimID string, request CloudNotificationHandlingRequest, idempotencyKey string) (*CloudNotificationHandlingResult, error)

func (*HTTPNotificationClient) ReleaseNotification

func (c *HTTPNotificationClient) ReleaseNotification(ctx context.Context, profile AgentProfile, claimID string, idempotencyKey string) (*CloudNotificationClaimMutation, error)

func (*HTTPNotificationClient) RenewNotification

func (c *HTTPNotificationClient) RenewNotification(ctx context.Context, profile AgentProfile, claimID string, leaseTTL time.Duration, idempotencyKey string) (*CloudNotificationLease, error)

func (*HTTPNotificationClient) WaitNotificationWake

func (c *HTTPNotificationClient) WaitNotificationWake(ctx context.Context, profile AgentProfile) (*CloudNotificationWake, error)

type LastNudgeRecord

type LastNudgeRecord struct {
	MessageID       *string `json:"message_id"`
	PaneTarget      *string `json:"pane_target"`
	AttemptedAt     *string `json:"attempted_at"`
	SucceededAt     *string `json:"succeeded_at"`
	ClaimGeneration *string `json:"claim_generation,omitempty"`
	AttemptCount    int     `json:"attempt_count,omitempty"`
	NextEligibleAt  *string `json:"next_eligible_at,omitempty"`
	FailureReason   string  `json:"failure_reason,omitempty"`
	Stuck           bool    `json:"stuck,omitempty"`
}

type LocalMessageRecipient

type LocalMessageRecipient struct {
	Profile    string
	BotName    string
	BotID      string
	BotAgentID string
}

type LocalMessageSend

type LocalMessageSend struct {
	SenderProfile    string
	SenderBotName    string
	SenderBotID      string
	SenderBotAgentID string
	Recipients       []LocalMessageRecipient
	Body             MessageBody
	Refs             map[string]any
	ThreadID         *string
	IdempotencyKey   string
	Now              time.Time
}

type LocalMessageSendResult

type LocalMessageSendResult struct {
	OutboxID       string
	Messages       []MessageEnvelope
	DispatchErrors []MessageDispatchError
	Replayed       bool
}

type ManagedSessionSetting

type ManagedSessionSetting struct {
	Enabled  bool   `json:"enabled"`
	Runtime  string `json:"runtime"`
	Model    string `json:"model,omitempty"`
	Host     string `json:"host,omitempty"`
	Timezone string `json:"timezone,omitempty"`
}

type MessageBody

type MessageBody struct {
	Format  string `json:"format"`
	Content string `json:"content"`
}

func NotificationMessageBody

func NotificationMessageBody(notification CloudNotification) MessageBody

type MessageClaimOptions

type MessageClaimOptions struct {
	Profile           string
	MessageID         string
	ClaimHolder       string
	SessionID         *string
	SessionScopeType  *string
	SessionScopeID    *string
	SessionGeneration *string
	LeaseTTL          time.Duration
	Now               time.Time
}

type MessageDelivery

type MessageDelivery struct {
	State             string              `json:"state"`
	ClaimHolder       *string             `json:"claim_holder"`
	SessionID         *string             `json:"session_id"`
	SessionScope      MessageSessionScope `json:"session_scope"`
	SessionGeneration *string             `json:"session_generation"`
	ReadAt            *string             `json:"read_at"`
	LeaseExpiresAt    *string             `json:"lease_expires_at"`
}

type MessageDispatchError

type MessageDispatchError struct {
	MessageID string `json:"message_id"`
	Profile   string `json:"profile"`
	Code      string `json:"code"`
	Message   string `json:"message"`
	Retryable bool   `json:"retryable"`
}

type MessageEnvelope

type MessageEnvelope struct {
	ID         string          `json:"id"`
	Version    int             `json:"version"`
	Kind       string          `json:"kind"`
	Source     string          `json:"source"`
	Profile    string          `json:"profile"`
	BotName    string          `json:"bot_name"`
	BotID      string          `json:"bot_id,omitempty"`
	BotAgentID string          `json:"bot_agent_id,omitempty"`
	From       string          `json:"from"`
	To         []string        `json:"to"`
	ThreadID   *string         `json:"thread_id"`
	CreatedAt  string          `json:"created_at"`
	Body       MessageBody     `json:"body"`
	Refs       map[string]any  `json:"refs"`
	Delivery   MessageDelivery `json:"delivery"`
}

type MessageListFilter

type MessageListFilter struct {
	Profile                   string
	BotName                   string
	BotID                     string
	BotAgentID                string
	AllowIdentityProfileDrift bool
	Source                    string
	State                     string
	Holder                    string
	ActiveOnly                bool
	Limit                     int
	Cursor                    string
	Kinds                     []string
}

type MessageSessionScope

type MessageSessionScope struct {
	Type *string `json:"type"`
	ID   *string `json:"id"`
}

type MessageSpoolEntry

type MessageSpoolEntry struct {
	Version        int             `json:"version"`
	Host           string          `json:"host,omitempty"`
	MessageID      string          `json:"message_id"`
	Profile        string          `json:"profile"`
	BotName        string          `json:"bot_name"`
	Source         string          `json:"source"`
	Kind           string          `json:"kind"`
	DeliveryState  string          `json:"delivery_state"`
	CreatedAt      string          `json:"created_at"`
	UpdatedAt      string          `json:"updated_at"`
	LeaseExpiresAt *string         `json:"lease_expires_at,omitempty"`
	SessionID      *string         `json:"session_id,omitempty"`
	Generation     *string         `json:"generation,omitempty"`
	PaneTarget     *string         `json:"pane_target,omitempty"`
	LastNudge      LastNudgeRecord `json:"last_nudge"`
}

func ListMessageSpool

func ListMessageSpool(paths Paths, profile string, botName string) ([]MessageSpoolEntry, error)

func ReadMessageSpool

func ReadMessageSpool(paths Paths, profile string, messageID string) (MessageSpoolEntry, bool, error)

type MessageWaitSummary

type MessageWaitSummary struct {
	MessageID      string         `json:"message_id"`
	Profile        string         `json:"profile"`
	BotName        string         `json:"bot_name"`
	BotID          string         `json:"bot_id,omitempty"`
	BotAgentID     string         `json:"bot_agent_id,omitempty"`
	Kind           string         `json:"kind"`
	Source         string         `json:"source"`
	CreatedAt      string         `json:"created_at"`
	Refs           map[string]any `json:"refs,omitempty"`
	LeaseExpiresAt *string        `json:"lease_expires_at,omitempty"`
}

type NotificationClient

type NotificationClient interface {
	LeaseNotification(ctx context.Context, profile AgentProfile, leaseTTL time.Duration, leaseHolder string, idempotencyKey string, kinds ...string) (*CloudNotificationLease, error)
	RenewNotification(ctx context.Context, profile AgentProfile, claimID string, leaseTTL time.Duration, idempotencyKey string) (*CloudNotificationLease, error)
	AckNotification(ctx context.Context, profile AgentProfile, claimID string, idempotencyKey string) (*CloudNotificationClaimMutation, error)
	ReleaseNotification(ctx context.Context, profile AgentProfile, claimID string, idempotencyKey string) (*CloudNotificationClaimMutation, error)
	PublishNotificationHandlingActivity(ctx context.Context, profile AgentProfile, claimID string, request CloudNotificationHandlingRequest, idempotencyKey string) (*CloudNotificationHandlingResult, error)
}

type NotificationHTTPError

type NotificationHTTPError struct {
	Status int
	Code   string
}

func (*NotificationHTTPError) Error

func (e *NotificationHTTPError) Error() string

type NotificationPollerStatus

type NotificationPollerStatus struct {
	Profile          string  `json:"profile"`
	BotName          string  `json:"bot_name,omitempty"`
	State            string  `json:"state"`
	StartedAt        string  `json:"started_at"`
	StoppedAt        *string `json:"stopped_at,omitempty"`
	LastPollAt       *string `json:"last_poll_at,omitempty"`
	LastLeaseAt      *string `json:"last_lease_at,omitempty"`
	LastErrorAt      *string `json:"last_error_at,omitempty"`
	LastErrorCode    *string `json:"last_error_code,omitempty"`
	LastErrorMessage *string `json:"last_error_message,omitempty"`
}

type NotificationWakeClient

type NotificationWakeClient interface {
	WaitNotificationWake(ctx context.Context, profile AgentProfile) (*CloudNotificationWake, error)
}

type Paths

type Paths struct {
	Home   string
	Socket string
	// BusTCPAddr, when non-empty, is the TCP address the CLIENT dials instead of
	// the Unix socket — an opt-in transport for environments where a bind-mounted
	// Unix socket can't be reached across a boundary (e.g. Docker Desktop on
	// macOS). It is CLIENT-only: the daemon's own bind address is
	// DaemonOptions.TCPListenAddr, kept separate so a host that exports the dial
	// address doesn't make a native daemon try to bind it. Injected at the
	// CLI/socket boundary (resolveCLIPaths) from COMMENT_IO_BUS_TCP_ADDR — not in
	// this generic resolver, so library/test Paths don't inherit an ambient value.
	BusTCPAddr      string
	PID             string
	Logs            string
	Bus             string
	History         string
	Ops             string
	OpsPending      string
	OpsDone         string
	Sessions        string
	Capabilities    string
	OwnerCapability string
	Private         string
	Spool           string
}

func ResolvePaths

func ResolvePaths(home string) (Paths, error)

type PeerCredential

type PeerCredential struct {
	UID uint32
	GID uint32
	PID int32
}

func PeerCredentialFor

func PeerCredentialFor(conn *net.UnixConn) (PeerCredential, error)

type PrivateCloudMessageMetadata

type PrivateCloudMessageMetadata struct {
	LocalMessageID string `json:"local_message_id"`
	Source         string `json:"source"`
	Profile        string `json:"profile"`
	BaseURL        string `json:"base_url"`
	NotificationID string `json:"notification_id"`
	ClaimID        string `json:"claim_id"`
	ClaimedAt      string `json:"claimed_at"`
	LeaseExpiresAt string `json:"lease_expires_at"`
	AccessToken    string `json:"access_token,omitempty"`
}

func ReadPrivateCloudMessageMetadata

func ReadPrivateCloudMessageMetadata(paths Paths, profile string, messageID string) (PrivateCloudMessageMetadata, error)

type ProfileAlias

type ProfileAlias struct {
	Alias              string
	AliasOf            string
	BotID              string
	BotAgentID         string
	DisabledForPolling bool
	Path               string
}

type ProfileLoadOptions

type ProfileLoadOptions struct {
	Paths          Paths
	BotletsHome    string
	DefaultBaseURL string
}

type ProfileReloadError

type ProfileReloadError struct {
	Code    string              `json:"code"`
	Message string              `json:"message"`
	Profile string              `json:"profile,omitempty"`
	Bot     string              `json:"bot,omitempty"`
	Hints   []BotletsRepairHint `json:"hints,omitempty"`
}

func LoadAgentProfiles

func LoadAgentProfiles(_ context.Context, paths Paths, defaultBaseURL string) (map[string]AgentProfile, []ProfileReloadError)

func LoadAgentProfilesWithAliases

func LoadAgentProfilesWithAliases(_ context.Context, paths Paths, defaultBaseURL string) (map[string]AgentProfile, map[string]ProfileAlias, []ProfileReloadError)

func LoadBotletsRegistry

func LoadBotletsRegistry(_ context.Context, botletsHome string, profiles map[string]AgentProfile) (map[string]BotRegistryEntry, []ProfileReloadError)

func ValidateBotletsRegistryEntries

func ValidateBotletsRegistryEntries(botletsHome string, profiles map[string]AgentProfile, registryBots []BotRegistryEntry) (map[string]BotRegistryEntry, []ProfileReloadError)

type ProfileReloadResult

type ProfileReloadResult struct {
	Added          []string             `json:"added"`
	Removed        []string             `json:"removed"`
	Restarted      []string             `json:"restarted"`
	Errors         []ProfileReloadError `json:"errors"`
	ProfilesLoaded int                  `json:"profiles_loaded"`
	BotsLoaded     int                  `json:"bots_loaded"`
}

type ProfileState

type ProfileState struct {
	AgentProfiles  map[string]AgentProfile
	ProfileAliases map[string]ProfileAlias
	BotRegistry    map[string]BotRegistryEntry
	BotletsHome    string
}

func EmptyProfileState

func EmptyProfileState(botletsHome string) ProfileState

type RegisterSessionOptions

type RegisterSessionOptions struct {
	Paths             Paths
	Host              string
	Profile           string
	BotName           string
	BotID             string
	BotAgentID        string
	ScopeType         string
	ScopeID           string
	SessionID         string
	Generation        string
	BotletsHome       string
	SessionName       string
	PaneTarget        string
	Runtime           string
	Model             string
	RuntimeSessionRef string
	LaunchMode        string
	State             string
	Now               time.Time
}

type RepairAction

type RepairAction struct {
	Action    string `json:"action"`
	MessageID string `json:"message_id,omitempty"`
	OpID      string `json:"op_id,omitempty"`
	FromState string `json:"from_state,omitempty"`
	ToState   string `json:"to_state,omitempty"`
	Reason    string `json:"reason"`
}

func BusRepairDryRun

func BusRepairDryRun(ctx context.Context, paths Paths, store *Store) ([]RepairAction, error)

func BusRepairDryRunWithFilter

func BusRepairDryRunWithFilter(ctx context.Context, paths Paths, store *Store, filter RepairFilter) ([]RepairAction, error)

type RepairFilter

type RepairFilter struct {
	MessageID string
	OpID      string
}

type SessionExecOptions

type SessionExecOptions struct {
	Paths      Paths
	SessionID  string
	Generation string
	Environ    []string
	LookPath   func(string) (string, error)
	Exec       func(string, []string, []string) error
}

type SessionFileSnapshot

type SessionFileSnapshot struct {
	Path    string
	Exists  bool
	Size    int64
	Lines   int64
	ModTime time.Time
}

func ReadClaudeSessionFileSnapshot

func ReadClaudeSessionFileSnapshot(userHome string, cwd string, runtimeSessionRef string) (SessionFileSnapshot, error)

type SessionNudgeLocks

type SessionNudgeLocks struct {
	// contains filtered or unexported fields
}

type SessionRecord

type SessionRecord struct {
	SessionID          string   `json:"session_id"`
	Host               string   `json:"host,omitempty"`
	Profile            string   `json:"profile"`
	BotName            string   `json:"bot_name"`
	BotID              string   `json:"bot_id,omitempty"`
	BotAgentID         string   `json:"bot_agent_id,omitempty"`
	ScopeType          string   `json:"scope_type"`
	ScopeID            string   `json:"scope_id"`
	BotletsHome        string   `json:"botlets_home"`
	SessionName        string   `json:"session_name"`
	PaneTarget         string   `json:"pane_target"`
	Generation         string   `json:"generation"`
	CapabilityFile     string   `json:"capability_file"`
	Runtime            string   `json:"runtime"`
	Model              string   `json:"model,omitempty"`
	RuntimePath        string   `json:"runtime_path,omitempty"`
	RuntimeSessionRef  string   `json:"runtime_session_ref,omitempty"`
	RuntimeCommandPath string   `json:"runtime_command_path,omitempty"`
	RuntimeCommand     []string `json:"runtime_command"`
	// RuntimeLaunchMode: "path" (legacy trusted-binary exec) or "shell"
	// (resolve the runtime name through the user's login shell). Empty
	// normalizes to "path". See normalizeRuntimeLaunchMode.
	RuntimeLaunchMode string                     `json:"runtime_launch_mode,omitempty"`
	WorkingDir        string                     `json:"working_dir,omitempty"`
	OutputLogPath     string                     `json:"output_log_path,omitempty"`
	CreatedAt         string                     `json:"created_at"`
	StartupStartedAt  string                     `json:"startup_started_at,omitempty"`
	LastNudge         LastNudgeRecord            `json:"last_nudge"`
	AutomaticNudges   map[string]LastNudgeRecord `json:"automatic_nudges,omitempty"`
	DailyReset        *DailyResetRecord          `json:"daily_reset,omitempty"`
	State             string                     `json:"state"`
}

func ListSessionRecords

func ListSessionRecords(paths Paths) ([]SessionRecord, error)

ListSessionRecords reads all session records strictly: a single malformed or invalid record fails the whole read. This fail-closed behavior is relied on by safety-sensitive callers like `comment uninstall`, which must NOT proceed to delete state when it cannot positively account for every managed session. Liveness/status read paths that must tolerate a poisoned record instead use ListSessionRecordsLenient (see issue #1420 Bug 2).

func ListSessionRecordsLenient

func ListSessionRecordsLenient(paths Paths) ([]SessionRecord, []string, error)

ListSessionRecordsLenient reads every valid session record, skipping (rather than failing the whole batch on) any individual record that is malformed or fails validation. A single poisoned record must not abort the read: that previously surfaced to clients as `UPSTREAM_ERROR: could not read sessions` and took down `comment run` / `comment sessions status` whenever the relaunch loop leaked a dangling record (issue #1420). The skipped session IDs are returned so callers can log/quarantine them. Only a directory-level read failure is fatal.

func ReadSessionRecord

func ReadSessionRecord(paths Paths, sessionID string) (SessionRecord, error)

func RegisterSession

func RegisterSession(options RegisterSessionOptions) (SessionRecord, error)

func VerifySessionCapability

func VerifySessionCapability(paths Paths, auth SocketAuth) (SessionRecord, error)

func VerifySessionCapabilityForResetComplete

func VerifySessionCapabilityForResetComplete(paths Paths, auth SocketAuth) (SessionRecord, error)

type SocketAuth

type SocketAuth struct {
	Mode              string  `json:"mode"`
	Capability        string  `json:"capability"`
	Profile           *string `json:"profile,omitempty"`
	SessionID         *string `json:"session_id,omitempty"`
	SessionGeneration *string `json:"session_generation,omitempty"`
	// contains filtered or unexported fields
}

func (*SocketAuth) UnmarshalJSON

func (auth *SocketAuth) UnmarshalJSON(data []byte) error

type SocketError

type SocketError struct {
	Code      string `json:"code"`
	Message   string `json:"message"`
	Retryable bool   `json:"retryable"`
}

type SocketRequest

type SocketRequest struct {
	ID     string         `json:"id"`
	Op     string         `json:"op"`
	Auth   *SocketAuth    `json:"auth,omitempty"`
	Params map[string]any `json:"params"`
	// contains filtered or unexported fields
}

func (*SocketRequest) UnmarshalJSON

func (req *SocketRequest) UnmarshalJSON(data []byte) error

type SocketResponse

type SocketResponse struct {
	ID     string       `json:"id"`
	OK     bool         `json:"ok"`
	Result any          `json:"result,omitempty"`
	Error  *SocketError `json:"error,omitempty"`
}

func CallSocket

func CallSocket(ctx context.Context, paths Paths, req SocketRequest, responseTimeout time.Duration) (SocketResponse, error)

type Store

type Store struct {
	// contains filtered or unexported fields
}

func OpenExistingStore

func OpenExistingStore(ctx context.Context, paths Paths) (*Store, error)

func OpenStore

func OpenStore(ctx context.Context, paths Paths) (*Store, error)

func (*Store) AckCloudMessage

func (s *Store) AckCloudMessage(ctx context.Context, profile string, messageID string, claimHolder string, now time.Time) (MessageEnvelope, error)

func (*Store) AckMessage

func (s *Store) AckMessage(ctx context.Context, profile string, messageID string, claimHolder string, now time.Time) (MessageEnvelope, error)

func (*Store) BackfillBotIdentityColumns

func (s *Store) BackfillBotIdentityColumns(ctx context.Context, bots map[string]BotRegistryEntry) error

func (*Store) ClaimMessage

func (s *Store) ClaimMessage(ctx context.Context, options MessageClaimOptions) (MessageEnvelope, error)

func (*Store) Close

func (s *Store) Close() error

func (*Store) DeleteTransientRuntime

func (s *Store) DeleteTransientRuntime(ctx context.Context, runID string) error

func (*Store) GetInboxMessage

func (s *Store) GetInboxMessage(ctx context.Context, profile string, messageID string) (MessageEnvelope, error)

func (*Store) GetInboxMessageByID

func (s *Store) GetInboxMessageByID(ctx context.Context, messageID string) (MessageEnvelope, error)

func (*Store) HasActiveSessionClaim

func (s *Store) HasActiveSessionClaim(ctx context.Context, claimHolder string, botName string, now time.Time) (bool, error)

func (*Store) HasActiveSessionClaimExcluding

func (s *Store) HasActiveSessionClaimExcluding(ctx context.Context, claimHolder string, botName string, excludeMessageID string, now time.Time) (bool, error)

func (*Store) HasActiveSessionClaimForBot

func (s *Store) HasActiveSessionClaimForBot(ctx context.Context, claimHolder string, botName string, botID string, botAgentID string, now time.Time) (bool, error)

func (*Store) HasActiveSessionClaimForBotExcluding

func (s *Store) HasActiveSessionClaimForBotExcluding(ctx context.Context, claimHolder string, botName string, botID string, botAgentID string, excludeMessageID string, now time.Time) (bool, error)

func (*Store) InsertCloudNotificationMessage

func (s *Store) InsertCloudNotificationMessage(ctx context.Context, input CloudNotificationMessage) (MessageEnvelope, error)

func (*Store) InsertLocalMessages

func (s *Store) InsertLocalMessages(ctx context.Context, input LocalMessageSend) (LocalMessageSendResult, error)

func (*Store) ListClaimedMessages

func (s *Store) ListClaimedMessages(ctx context.Context) ([]MessageEnvelope, error)

func (*Store) ListCloudClaimsForHolder

func (s *Store) ListCloudClaimsForHolder(ctx context.Context, claimHolder string) ([]MessageEnvelope, error)

func (*Store) ListInboxMessages

func (s *Store) ListInboxMessages(ctx context.Context, filter MessageListFilter) ([]MessageEnvelope, error)

func (*Store) ListInboxMessagesByBotIdentity

func (s *Store) ListInboxMessagesByBotIdentity(ctx context.Context, botID string, botAgentID string, kinds []string, limit int, cursor string) ([]MessageEnvelope, error)

func (*Store) ListSentMessages

func (s *Store) ListSentMessages(ctx context.Context, senderProfile string, limit int, cursor string) ([]MessageEnvelope, error)

func (*Store) ListTransientRuntimes

func (s *Store) ListTransientRuntimes(ctx context.Context) ([]TransientRuntimeRecord, error)

func (*Store) Path

func (s *Store) Path() string

func (*Store) PutTransientRuntime

func (s *Store) PutTransientRuntime(ctx context.Context, record TransientRuntimeRecord) error

func (*Store) QuarantineCloudMessage

func (s *Store) QuarantineCloudMessage(ctx context.Context, profile string, messageID string, reason string, now time.Time) (MessageEnvelope, error)

func (*Store) RefreshCloudNotificationLease

func (s *Store) RefreshCloudNotificationLease(ctx context.Context, profile string, messageID string, leaseExpiresAt string, now time.Time, reopenClaimed bool) (quarantined bool, err error)

RefreshCloudNotificationLease applies a re-delivered cloud notification's lease. It returns quarantined=true when the message had been re-delivered too many times (the #301 redelivery loop) and was terminally stopped instead of being reopened, so the caller can warn-log the stuck loop.

func (*Store) ReleaseClaimsForHolder

func (s *Store) ReleaseClaimsForHolder(ctx context.Context, claimHolder string, reason string, now time.Time) ([]string, error)

func (*Store) ReleaseCloudMessage

func (s *Store) ReleaseCloudMessage(ctx context.Context, profile string, messageID string, claimHolder string, reason string, now time.Time) (MessageEnvelope, error)

func (*Store) ReleaseMessage

func (s *Store) ReleaseMessage(ctx context.Context, profile string, messageID string, claimHolder string, reason string, now time.Time) (MessageEnvelope, error)

func (*Store) RenewCloudMessage

func (s *Store) RenewCloudMessage(ctx context.Context, profile string, messageID string, claimHolder string, leaseExpiresAt string, now time.Time) (MessageEnvelope, error)

func (*Store) RenewMessage

func (s *Store) RenewMessage(ctx context.Context, profile string, messageID string, claimHolder string, ttl time.Duration, now time.Time) (MessageEnvelope, error)

func (*Store) RepairDryRun

func (s *Store) RepairDryRun(ctx context.Context) ([]RepairAction, error)

func (*Store) RequeueClaimedMessage

func (s *Store) RequeueClaimedMessage(ctx context.Context, profile string, messageID string, reason string, now time.Time) (MessageEnvelope, error)

func (*Store) RequeueCloudClaimsForHolder

func (s *Store) RequeueCloudClaimsForHolder(ctx context.Context, claimHolder string, reason string, now time.Time) ([]string, error)

func (*Store) RequeueCloudMessageLocally

func (s *Store) RequeueCloudMessageLocally(ctx context.Context, profile string, messageID string, claimHolder string, reason string, now time.Time) (MessageEnvelope, error)

func (*Store) SchemaVersion

func (s *Store) SchemaVersion(ctx context.Context) (int, error)

func (*Store) TableNames

func (s *Store) TableNames(ctx context.Context) ([]string, error)

func (*Store) UnclaimedCountsByProfile

func (s *Store) UnclaimedCountsByProfile(ctx context.Context) (map[string]int, error)

UnclaimedCountsByProfile returns, per profile, the number of unclaimed (queued, not-yet-picked-up) message recipients. Profiles with zero unclaimed messages are omitted. Surfaced in `comment daemon health` so an operator can tell at a glance whether a profile has a backlog piling up — not just whether the daemon is connected (bug #95). A non-empty count on a profile with no active notification poller is the silent-queue case the daemon used to hide.

func (*Store) WaitCloudNotificationMessage

func (s *Store) WaitCloudNotificationMessage(ctx context.Context, profile string, botName string) (*MessageEnvelope, error)

func (*Store) WaitMessageSummaries

func (s *Store) WaitMessageSummaries(ctx context.Context, filter MessageListFilter, limit int) ([]MessageWaitSummary, error)

func (*Store) WaitMessageSummary

func (s *Store) WaitMessageSummary(ctx context.Context, filter MessageListFilter) (*MessageWaitSummary, error)

type TmuxController

type TmuxController interface {
	HasSession(ctx context.Context, sessionName string) (bool, error)
	NewSession(ctx context.Context, options TmuxNewSessionOptions) error
	PaneTarget(ctx context.Context, sessionName string) (string, error)
	PaneBelongsToSession(ctx context.Context, sessionName string, paneTarget string) (bool, error)
	PaneCurrentCommand(ctx context.Context, paneTarget string) (string, error)
	CapturePane(ctx context.Context, paneTarget string, lines int) (string, error)
	SendLiteral(ctx context.Context, paneTarget string, text string) error
	// PasteText pushes multi-line text into the target pane via tmux's
	// paste-buffer (bracketed paste). Unlike SendLiteral, the text MAY
	// contain newlines. The buffer is consumed after pasting. Caller is
	// responsible for sending Enter to submit, just like with SendLiteral.
	PasteText(ctx context.Context, paneTarget string, text string) error
	SendEnter(ctx context.Context, paneTarget string) error
	KillSession(ctx context.Context, sessionName string) error
}

type TmuxNewSessionOptions

type TmuxNewSessionOptions struct {
	SessionName       string
	WorkingDir        string
	CommentHome       string
	BotletsHome       string
	Command           string
	OutputPipeCommand string
}

type TransientRuntimeRecord

type TransientRuntimeRecord struct {
	RunID              string   `json:"run_id"`
	Host               string   `json:"host,omitempty"`
	BmuxBinary         string   `json:"bmux_binary,omitempty"`
	Profile            string   `json:"profile"`
	Role               string   `json:"role"`
	BotName            string   `json:"bot_name"`
	BotID              string   `json:"bot_id,omitempty"`
	BotAgentID         string   `json:"bot_agent_id,omitempty"`
	SessionName        string   `json:"session_name"`
	PaneTarget         string   `json:"pane_target"`
	Runtime            string   `json:"runtime"`
	RuntimeCommand     []string `json:"runtime_command"`
	RuntimeCommandPath string   `json:"runtime_command_path"`
	CommentCommandPath string   `json:"comment_command_path,omitempty"`
	OutputLogPath      string   `json:"output_log_path,omitempty"`
	RuntimePath        string   `json:"runtime_path"`
	CWD                string   `json:"cwd"`
	Env                []string `json:"env,omitempty"`
	State              string   `json:"state"`
	StartedAt          string   `json:"started_at"`
	// RuntimeLaunchMode selects how the runtime is launched: "path" (legacy:
	// resolve+exec a trusted absolute binary path) or "shell" (resolve the
	// runtime name through the user's interactive login shell, supporting PATH
	// binaries, aliases, and functions). In "shell" mode RuntimePath /
	// RuntimeCommandPath carry no meaning and are empty. Empty normalizes to
	// "path" for legacy records.
	RuntimeLaunchMode string `json:"runtime_launch_mode,omitempty"`
}

type TransientRuntimeStatus

type TransientRuntimeStatus struct {
	Runtime TransientRuntimeRecord `json:"runtime"`
	Health  string                 `json:"health"`
}

Jump to

Keyboard shortcuts

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