Documentation
¶
Overview ¶
Issue #1143: idle-timeout JSON helpers.
These thin wrappers merge / extract the idle_timeout_secs field on the tool_data blob without changing the positional MarshalToolData / UnmarshalToolData signatures. The MergeToolDataExtras layer in statedb preserves keys outside the typed schema across INSERT OR REPLACE, so a row written by an old binary survives a round-trip through a new binary (and vice versa).
Issue #1143: central poll watcher that auto-stops sessions whose tmux pane content hasn't changed for `IdleTimeoutSecs` seconds.
Design (per RFC in PR body):
- One watcher per agent-deck process (TUI or daemon). It walks every instance on each Tick — no per-session goroutine.
- The "idle" signal is the FNV-1a hash of the tmux capture-pane content. Anything cheaper (status transitions) doesn't catch the dormant-worker case the self-improvement report flagged on 2026-05-21.
- On trigger: Stop callback fires + a single JSONL row is appended to ~/.agent-deck/logs/session-lifecycle.jsonl with action "idle-timeout-expired".
- Idempotent: once a session is stopped, its lastSeen entry is dropped so a Restart() re-enters the watcher fresh.
Index ¶
- Constants
- Variables
- func AckCompletion(profile, childID string) error
- func AddSkillSource(name, path, description string) error
- func ApplyConfiguredLoadout(inst *Instance) []string
- func ApplyMultiRepoClaudeContext(tool string, multiRepoEnabled bool, claudeJSONPath, parentDir string, ...) error
- func ApplyMultiRepoCodexContext(tool string, multiRepoEnabled bool, parentDir string) error
- func ApplyProjectSkills(projectPath, tool string, desired []SkillCandidate) error
- func ArchiveTimeUTC(t time.Time) time.Time
- func BridgeDaemonHint() string
- func CheckClaudeHooksInstalled(configDir string) bool
- func CheckCursorHooksInstalled(configDir string) bool
- func CheckGeminiHooksInstalled(configDir string) bool
- func CheckHermesHooksInstalled(configDir string) bool
- func ChildLaunchEnv(inst *Instance, childConfigDir string) []string
- func ClaudeSessionName(sessionID string) string
- func ClaudeSessionNameIn(claudeDir, sessionID string) string
- func CleanStaleEventFiles()
- func CleanStaleSSHSockets()
- func ClearAllCodexMCPInfoCache()
- func ClearAllCursorMCPInfoCache()
- func ClearAllOpenCodeMCPInfoCache()
- func ClearCodexMCPCache(codexHome string)
- func ClearCursorMCPCache(projectPath string)
- func ClearFocusRequest(db *statedb.StateDB) error
- func ClearHookSessionAnchor(instanceID string)
- func ClearMCPCache(projectPath string)
- func ClearOpenCodeMCPCache(projectPath string)
- func ClearProjectMCPs(projectPath string) error
- func ClearUserConfigCache()
- func CommitToInbox(parentSessionID string, event TransitionNotificationEvent) error
- func CompletionRecordExists(profile, childID string) bool
- func ConductorDefaultDir() (string, error)
- func ConductorDir() (string, error)
- func ConductorNameDir(name string) (string, error)
- func ConductorProfileDir(profile string) (string, error)
- func ConductorSessionTitle(name string) string
- func ConductorSystemActive() bool
- func ConfiguredHiddenToolNames() []string
- func ConsumedTurnsDir() string
- func ConvertToClaudeDirName(path string) string
- func CountRunningInGroup(instances []*Instance, groupPath string) int
- func CreateExampleConfig() error
- func CreateProfile(profile string) error
- func DeadLetterDir() string
- func DeadLetterPathFor(childSessionID string) string
- func DecodeFocusRequest(val string, nowNano int64, ttl time.Duration) (id string, fresh bool)
- func DecodeFocusRequestAttach(val string, nowNano int64, ttl time.Duration) (id string, attach bool, fresh bool)
- func DeduplicateDirnames(paths []string) []string
- func DeleteProfile(profile string) error
- func DetectConductorDirSplitBrain() (string, bool)
- func DiscoverHermesGatewayURL() string
- func EmitTelegramChannelDriftWarning(title, instanceID, configDir string, channels []string, ...)
- func EncodeFocusRequest(id string, nowNano int64) (string, error)
- func EncodeFocusRequestAttach(id string, nowNano int64, attach bool) (string, error)
- func EventFingerprint(e TransitionNotificationEvent) string
- func ExpandPath(path string) string
- func FilterVisibleToolNames(names []string) []string
- func FindAgentDeck() string
- func ForgetConsumedTurnsForChild(childSessionID string)
- func FormatCompletionsForInjection(events []TransitionNotificationEvent) string
- func GenerateHeartbeatPlist(name string, intervalMinutes int) (string, error)
- func GenerateID() string
- func GenerateLaunchdPlist() (string, error)
- func GenerateSessionName() string
- func GenerateSystemdBridgeService() (string, error)
- func GenerateSystemdHeartbeatService(name string) (string, error)
- func GenerateSystemdHeartbeatTimer(name string, intervalMinutes int) string
- func GenerateSystemdTransitionNotifierService() (string, error)
- func GenerateTransitionNotifierLaunchdPlist() (string, error)
- func GenerateUniqueSessionName(instances []*Instance, groupPath string) string
- func GetAgentDeckDir() (string, error)
- func GetAvailableGeminiModels() ([]string, error)
- func GetAvailableMCPNames() []string
- func GetAvailableMCPs() map[string]MCPDef
- func GetAvailablePluginNames() []string
- func GetAvailablePlugins() map[string]PluginDef
- func GetClaudeCommand() string
- func GetClaudeCommandForInstance(inst *Instance) string
- func GetClaudeConfigDir() string
- func GetClaudeConfigDirForGroup(groupPath string) string
- func GetClaudeConfigDirForInstance(inst *Instance) string
- func GetClaudeConfigDirSourceForGroup(groupPath string) (path, source string)
- func GetClaudeConfigDirSourceForInstance(inst *Instance) (path, source string)
- func GetClaudeSessionID(projectPath string) (string, error)
- func GetCodexCommand() string
- func GetCodexConfigDir() string
- func GetCodexConfigPath(codexHome string) string
- func GetConductorLastActivity(name, profile string) (time.Time, error)
- func GetConfigPath() (string, error)
- func GetCrushCommand() string
- func GetCursorConfigDir() string
- func GetCustomToolNames() []string
- func GetDBPathForProfile(profile string) (string, error)
- func GetDefaultTool() string
- func GetDirectoryCompletions(input string) ([]string, error)
- func GetEffectiveProfile(explicit string) string
- func GetEventsDir() string
- func GetGeminiConfigDir() string
- func GetGeminiMCPNames() []string
- func GetGeminiSessionsDir(projectPath string) string
- func GetGlobalHTTPPool() *mcppool.HTTPPool
- func GetGlobalMCPNames() []string
- func GetGlobalPool() *mcppool.Pool
- func GetGlobalPoolRunningCount() int
- func GetGroupLevel(path string) int
- func GetHTTPServerStatus(name string) string
- func GetHermesConfigDir() string
- func GetHermesGatewayURL() string
- func GetHooksDir() string
- func GetHotkeyOverrides() map[string]string
- func GetMCPDefaultScope() string
- func GetManageMCPJson() bool
- func GetOpenCodeConfigDir() string
- func GetProfileDir(profile string) (string, error)
- func GetProfilesDir() (string, error)
- func GetProjectMCPNames(projectPath string) []string
- func GetProjectSkillsDir(tool string) (string, bool)
- func GetProjectSkillsManifestPath(projectPath string) string
- func GetProjectSkillsPath(projectPath, tool string) string
- func GetSessionIDLifecycleLogPath() string
- func GetSessionLifecycleLogPath() string
- func GetSkillPoolPath() (string, error)
- func GetSkillSourcesPath() (string, error)
- func GetSkillsRootPath() (string, error)
- func GetTheme() string
- func GetToolBusyPatterns(toolName string) []string
- func GetToolCommand(toolName string) string
- func GetToolIcon(toolName string) string
- func GetUserConfigPath() (string, error)
- func GetUserMCPNames() []string
- func GetUserMCPRootPath() string
- func GetWebMutationsEnabled() bool
- func GroupByProject(instances []*Instance) map[string][]*Instance
- func GroupMaxConcurrent(tree *GroupTree, groupPath string) int
- func GroupPathForProject(projectPath string) string
- func HashProjectPath(projectPath string) string
- func HeartbeatDaemonStale(name string) bool
- func HeartbeatPlistLabel(name string) string
- func HeartbeatPlistPath(name string) (string, error)
- func HermesSharedWorkspaceDir() string
- func HookSessionAnchorPath(instanceID string) string
- func InboxDir() string
- func InboxHasPending(parentID string) bool
- func InboxPathFor(parentSessionID string) string
- func InboxTTL() time.Duration
- func InitializeGlobalPool(ctx context.Context, config *UserConfig, sessions []*Instance) (*mcppool.Pool, error)
- func InjectClaudeHooks(configDir string) (bool, error)
- func InjectCursorHooks(configDir string) (bool, error)
- func InjectGeminiHooks(configDir string) (bool, error)
- func InjectHermesHooks(configDir string) (bool, error)
- func InstallBridgeDaemon() (string, error)
- func InstallBridgeScript() error
- func InstallHeartbeatDaemon(name, profile string, intervalMinutes int) error
- func InstallHeartbeatScript(name, profile string) error
- func InstallLearningsMD() error
- func InstallPolicyMD(customPath string) error
- func InstallSharedClaudeMD(customPath string) error
- func InstallSharedConductorInstructions(agent, customPath string) error
- func InstallTransitionNotifierDaemon() (string, error)
- func InvalidateProjectMCPIntegrationsCache(projectPath string)
- func IsAtCap(running, max int) bool
- func IsBridgeDaemonRunning() bool
- func IsClaudeCompatible(toolName string) bool
- func IsClaudeConfigDirExplicit() bool
- func IsClaudeConfigDirExplicitForGroup(groupPath string) bool
- func IsClaudeConfigDirExplicitForInstance(inst *Instance) bool
- func IsCodexCompatible(toolName string) bool
- func IsConductorHeartbeatMessage(message string) bool
- func IsConductorSetup(name string) bool
- func IsHTTPServerRunning(name string) bool
- func IsHermesGatewayReachable(gatewayURL string) bool
- func IsRemovableWorktree(inst *Instance) bool
- func IsTelegramOfficialRefusal(name, source string) bool
- func IsTransitionNotifierDaemonRunning() bool
- func IsValidEnvKey(key string) bool
- func IsValidSessionColor(v string) bool
- func LaunchdPlistPath() (string, error)
- func ListProfiles() ([]string, error)
- func LoadSkillSources() (map[string]SkillSourceDef, error)
- func LocateConversationConfigDir(cfg *UserConfig, inst *Instance, extraCandidates ...string) (configDir, sessionID string, size int64)
- func LogCgroupIsolationDecision()
- func MCPGlobalConfigPathForTool(toolName string) string
- func MCPLocalConfigPathForTool(toolName, projectPath string) string
- func MarshalToolOptions(opts ToolOptions) (json.RawMessage, error)
- func MatchTool(cmd string) string
- func MergeToolPatterns(toolName string) *tmux.RawPatterns
- func MigrateClaudeProjectDir(home, oldProjectPath, newProjectPath string, copy bool) error
- func MigrateConductorHeartbeatScripts() ([]string, error)
- func MigrateConductorLearnings() ([]string, error)
- func MigrateConductorPolicySplit() ([]string, error)
- func MigrateConversation(inst *Instance, targetConfigDir string) (string, error)
- func MigrateConversationFrom(inst *Instance, srcConfigDir, targetConfigDir string) (string, error)
- func MigrateLegacyConductors() ([]string, error)
- func NeedsMigration() (bool, error)
- func OtherSessionsShareWorktree(target *Instance, others []*Instance) bool
- func ParseIdleTimeoutFlag(value string) (int64, error)
- func PickerToolNames() []string
- func PreAcceptClaudeTrust(claudeJSONPath, parentDir string) error
- func PreAcceptCodexSandboxWorkspaceTrust(homeDir string) error
- func PreAcceptCodexTrust(codexConfigPath, projectDir string) error
- func PreAcceptCursorTrust(cursorConfigDir, workspacePath string) error
- func PreAcceptCursorTrustInContainer(containerName, workspacePath string) error
- func PreAcceptCursorTrustSSH(host, workspacePath string) error
- func ProfileExists(profile string) (bool, error)
- func PruneCodexMCPCache(maxAge time.Duration)
- func PruneCursorMCPCache(maxAge time.Duration)
- func PruneMCPCache(maxAge time.Duration)
- func PruneOpenCodeMCPCache(maxAge time.Duration)
- func ReadFocusRequest(db *statedb.StateDB) (string, error)
- func ReadHookSessionAnchor(instanceID string) string
- func ReadIdleTimeoutSecsFromToolData(td json.RawMessage) int64
- func ReadPrevEventStatus(instanceID string) string
- func ReconcileDeclarativeGroups(tree *GroupTree, cfg *UserConfig) bool
- func RefreshInstancesForCLIStatus(instances []*Instance)
- func RefreshStalePluginPins(mcpFile string, profileDirs []string) (int, error)
- func RemoveClaudeHooks(configDir string) (bool, error)
- func RemoveCursorHooks(configDir string) (bool, error)
- func RemoveGeminiHooks(configDir string) (bool, error)
- func RemoveHeartbeatPlist(name string) error
- func RemoveHermesHooks(configDir string) (bool, error)
- func RemoveNotifyStateRecord(childSessionID string) (bool, error)
- func RemoveSessionWorktree(inst *Instance) (removed bool, err error)
- func RemoveSessionWorktreeUnlessShared(inst *Instance, others []*Instance) (removed bool, err error)
- func RemoveSkillSource(name string) error
- func ResetInboxFingerprintCacheForTest()
- func ResetStopBlockBudget(instanceID string)
- func ResolveCostLineTemplate(cfg *UserConfig, profile string) (template string, hideWhenZero bool)
- func ResolveTheme() string
- func RestoreFromArchive(baseDir string) error
- func RestoreOrphanedConversationBackup(inst *Instance, configDir string) (string, error)
- func SaveConductorMeta(meta *ConductorMeta) error
- func SaveConfig(config *Config) error
- func SaveProjectSkillsManifest(projectPath string, manifest *ProjectSkillsManifest) error
- func SaveSkillSources(sources map[string]SkillSourceDef) error
- func SaveUserConfig(config *UserConfig) error
- func SaveUserConfigWithIntent(config *UserConfig, allowSectionDrop bool) error
- func SaveWatcherMeta(meta *WatcherMeta) error
- func ScrubProcessEnvForChildLaunch(inst *Instance)
- func SelfHealAuditPath(profile string) (string, error)
- func SendSessionMessageReliable(profile, sessionRef, message string) error
- func SetDefaultProfile(profile string) error
- func SetField(inst *Instance, field, value string, extraArgsTokens []string) (oldValue string, postCommit func(), err error)
- func SetFsyncHookForTest(fn func(*os.File) error) func()
- func SetGroupSortMode(mode string)
- func SetSSHRunnerRunFnForTest(r *SSHRunner, fn func(args ...string) ([]byte, error))
- func SetupConductor(name, profile string, heartbeatEnabled bool, clearOnCompact bool, ...) error
- func SetupConductorProfile(profile string) error
- func SetupConductorWithAgent(name, profile, agent string, heartbeatEnabled bool, clearOnCompact bool, ...) error
- func ShouldNotifyTransition(fromStatus, toStatus string) bool
- func ShouldQueue(instances []*Instance, groupPath string, maxConcurrent int) bool
- func ShouldRecycleForVersion(running, onDisk string) bool
- func ShouldRestartProjectSkills(tool string) bool
- func ShouldSkipRestart(inst *Instance, now time.Time, force bool) (bool, string)
- func ShutdownGlobalPool(shouldShutdown bool) error
- func SlugifyClaudeProjectPath(projectPath string) string
- func SortInstancesByActionable(insts []*Instance)
- func StartHTTPServer(name string, def *MCPDef) error
- func StartMaintenanceWorker(ctx context.Context, onComplete func(MaintenanceResult))
- func StreamTranscript(ctx context.Context, path, sessionID string, sentAt time.Time, w io.Writer, ...) error
- func StripResumeFields(raw json.RawMessage) json.RawMessage
- func SupportsLaunchModel(tool string) bool
- func SupportsProjectSkills(tool string) bool
- func SweepInboxByTTL(maxAge time.Duration) (int, error)
- func SweepInboxByTuple(parentSessionID, childSessionID, fromStatus, toStatus string) (int, error)
- func SweepInboxesForChildSession(childSessionID string) (int, error)
- func SyncPluginChannels(inst *Instance)
- func SystemdBridgeServicePath() (string, error)
- func SystemdHeartbeatServiceName(name string) string
- func SystemdHeartbeatServicePath(name string) (string, error)
- func SystemdHeartbeatTimerName(name string) string
- func SystemdHeartbeatTimerPath(name string) (string, error)
- func SystemdTransitionNotifierServicePath() (string, error)
- func SystemdUserDir() (string, error)
- func TakeFocusRequest(db *statedb.StateDB) (string, error)
- func TeardownConductor(name string) error
- func TeardownConductorProfile(profile string) error
- func ThemeColorFGBG() string
- func TierName(tier SearchTier) string
- func ToggleLocalMCP(projectPath, mcpName string) error
- func ToolFilterActive() bool
- func ToolFilterFallbackActive() bool
- func ToolSupportsMCPManager(toolName string) bool
- func TranscriptTailLines(path string, n int) ([]string, error)
- func TransitionNotifierDaemonHint() string
- func TransitionNotifierLaunchdPlistPath() (string, error)
- func TriageDir() (string, error)
- func TurnFingerprint(e TransitionNotificationEvent) string
- func UninstallBridgeDaemon() error
- func UninstallHeartbeatDaemon(name string) error
- func UninstallTransitionNotifierDaemon() error
- func UpdateClaudeSessionsWithDedup(instances []*Instance)
- func UpdateGeminiAnalyticsFromDisk(projectPath, sessionID string, analytics *GeminiSessionAnalytics) error
- func UsesClaudeDeliveryVerify(toolName string) bool
- func ValidateClaudeExtraArgToken(token string) error
- func ValidateConductorName(name string) error
- func ValidateSSHHost(host string) error
- func ValidateTranscriptPath(path string) (string, bool)
- func VerifyConversationInDir(inst *Instance, cfgDir string, wantSize int64) error
- func VisibleToolNames() []string
- func WatcherDir() (string, error)
- func WatcherNameDir(name string) (string, error)
- func WorkerScratchDirRoot() (string, error)
- func WriteCodexMCPConfig(codexHome string, enabledNames []string) error
- func WriteCompletionRecord(rec CompletionRecord) error
- func WriteConductorClaudeSettings(name string) error
- func WriteCursorGlobalMCP(enabledNames []string) error
- func WriteCursorProjectMCP(projectPath string, enabledNames []string) error
- func WriteFocusRequest(db *statedb.StateDB, id string, nowNano int64) error
- func WriteFocusRequestAttach(db *statedb.StateDB, id string, nowNano int64, attach bool) error
- func WriteGeminiMCPSettings(enabledNames []string) error
- func WriteGlobalMCP(enabledNames []string) error
- func WriteGlobalMCPConfigForTool(toolName string, names []string) error
- func WriteHookSessionAnchor(instanceID, sessionID string)
- func WriteIdleTimeoutSecsToToolData(td json.RawMessage, secs int64) json.RawMessage
- func WriteInboxEvent(parentSessionID string, event TransitionNotificationEvent) error
- func WriteLedgerEntry(e CompletionLedgerEntry) error
- func WriteLocalMCPConfigForTool(toolName, projectPath string, names []string) error
- func WriteMCPJsonFromConfig(projectPath string, enabledNames []string) error
- func WriteMergedMcpJSONFile(mcpFile string, enabledNames []string, pluginPinClaudeProfile string) error
- func WriteMultiRepoParentClaudeMD(parentDir string, repoNames []string) error
- func WriteMultiRepoParentSettings(parentDir string, repoNames []string) error
- func WriteOpenCodeGlobalMCP(enabledNames []string) error
- func WriteOpenCodeProjectMCP(projectPath string, enabledNames []string) error
- func WriteSessionIDLifecycleEvent(event SessionIDLifecycleEvent) error
- func WriteSessionLifecycleEvent(ev SessionLifecycleEvent) error
- func WriteStatusEvent(event StatusEvent) error
- func WriteUserMCP(enabledNames []string) error
- func ZoxideAvailable() bool
- func ZoxideQuery(ctx context.Context, query string) ([]string, error)
- type AnalyticsDisplaySettings
- type BillingBlock
- type BudgetSettings
- type ClaudeConfig
- type ClaudeOptions
- type ClaudeProject
- type ClaudeSettings
- type CodexOptions
- type CodexSettings
- type CompletionCycler
- type CompletionLedgerEntry
- type CompletionRecord
- type ConductorAgentSpec
- type ConductorClaudeSettings
- type ConductorDirMigrateAction
- type ConductorDirMigrateOptions
- type ConductorDirMigrateResult
- type ConductorHermesSettings
- type ConductorMeta
- type ConductorOverrides
- type ConductorSettings
- type Config
- type ContentBuffer
- type CopilotOptions
- type CopilotSettings
- type CostsSettings
- type CrushOptions
- type CrushSettings
- type DeadLetterSink
- type DiscordSettings
- type DisplaySettings
- type DockerSettings
- type DoneSignal
- type ExperimentsSettings
- type FeedbackSettings
- type FieldRestartPolicy
- type FileTracker
- type FlickerDetector
- type FocusRequest
- type ForkSettings
- type GeminiMCPConfig
- type GeminiModelPricing
- type GeminiSessionAnalytics
- type GeminiSessionInfo
- type GeminiSettings
- type GlobalSearchIndex
- func (idx *GlobalSearchIndex) Close()
- func (idx *GlobalSearchIndex) EntryCount() int
- func (idx *GlobalSearchIndex) FuzzySearch(query string) []*SearchResult
- func (idx *GlobalSearchIndex) GetTier() SearchTier
- func (idx *GlobalSearchIndex) IsLoading() bool
- func (idx *GlobalSearchIndex) Search(query string) []*SearchResult
- type GlobalSearchSettings
- type Group
- type GroupActivity
- type GroupBudget
- type GroupClaudeResolution
- type GroupClaudeSettings
- type GroupData
- type GroupDefaultsSettings
- type GroupHermesSettings
- type GroupSettings
- type GroupTree
- func (t *GroupTree) AddSession(inst *Instance)
- func (t *GroupTree) CollapseGroup(path string)
- func (t *GroupTree) CreateGroup(name string) *Group
- func (t *GroupTree) CreateGroupPath(path string) *Group
- func (t *GroupTree) CreateSubgroup(parentPath, name string) *Group
- func (t *GroupTree) DefaultPathForGroup(groupPath string) string
- func (t *GroupTree) DeleteGroup(path string) []*Instance
- func (t *GroupTree) DemoteSession(inst *Instance)
- func (t *GroupTree) ExpandGroup(path string)
- func (t *GroupTree) ExpandGroupWithParents(path string)
- func (t *GroupTree) Flatten() []Item
- func (t *GroupTree) GetAllInstances() []*Instance
- func (t *GroupTree) GetGroupNames() []string
- func (t *GroupTree) GetGroupPaths() []string
- func (t *GroupTree) GroupActivityMap(viewArchived bool) map[string]GroupActivity
- func (t *GroupTree) GroupCount() int
- func (t *GroupTree) MoveGroupDown(path string)
- func (t *GroupTree) MoveGroupTo(sourcePath, destParentPath string) error
- func (t *GroupTree) MoveGroupUp(path string)
- func (t *GroupTree) MoveSessionDown(inst *Instance)
- func (t *GroupTree) MoveSessionToGroup(inst *Instance, newGroupPath string)
- func (t *GroupTree) MoveSessionUp(inst *Instance)
- func (t *GroupTree) PromoteSession(inst *Instance)
- func (t *GroupTree) RemoveSession(inst *Instance)
- func (t *GroupTree) RenameGroup(oldPath, newName string) error
- func (t *GroupTree) RenameTargetPath(oldPath, newName string) string
- func (t *GroupTree) SessionCount() int
- func (t *GroupTree) SessionCountForGroup(groupPath string) int
- func (t *GroupTree) SetDefaultPathForGroup(groupPath, defaultPath string) bool
- func (t *GroupTree) ShallowCopyForSave() *GroupTree
- func (t *GroupTree) SyncWithInstances(instances []*Instance)
- func (t *GroupTree) ToggleGroup(path string)
- type GroupViewMode
- type HTTPServerConfig
- type HermesOptions
- type HermesSettings
- type HookStatus
- type IdleTimeoutWatcher
- type IdleTimeoutWatcherConfig
- type Instance
- func DiscoverExistingTmuxSessions(existingInstances []*Instance) ([]*Instance, error)
- func FilterByQuery(instances []*Instance, query string) []*Instance
- func FilterInstancesByArchive(instances []*Instance, archived bool) []*Instance
- func FindNextQueued(instances []*Instance, groupPath string) *Instance
- func NewInstance(title, projectPath string) *Instance
- func NewInstanceWithGroup(title, projectPath, groupPath string) *Instance
- func NewInstanceWithGroupAndTool(title, projectPath, groupPath, tool string) *Instance
- func NewInstanceWithTool(title, projectPath, tool string) *Instance
- func (inst *Instance) AllProjectPaths() []string
- func (i *Instance) ApplyLaunchModel(model string) error
- func (i *Instance) CachedSubstate() Substate
- func (i *Instance) CanFork() bool
- func (i *Instance) CanForkCodex() bool
- func (i *Instance) CanForkOpenCode() bool
- func (i *Instance) CanForkPi() bool
- func (i *Instance) CanRestart() bool
- func (i *Instance) CanRestartFresh() bool
- func (i *Instance) CanRestartGeneric() bool
- func (i *Instance) CaptureLoadedMCPs()
- func (i *Instance) ClaudeSessionIDCollidesWith(peers []*Instance) bool
- func (inst *Instance) CleanupMultiRepoTempDir() error
- func (i *Instance) CleanupWorkerScratchConfigDir()
- func (i *Instance) ClearHookStatus()
- func (i *Instance) ClearLaunchModel() error
- func (inst *Instance) ClearParent()
- func (i *Instance) ConductorClearOnCompact() bool
- func (i *Instance) ConsumeCodexRestartWarning() string
- func (i *Instance) CreateForkedCodexInstanceWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
- func (i *Instance) CreateForkedInstance(newTitle, newGroupPath string) (*Instance, string, error)
- func (i *Instance) CreateForkedInstanceForTool(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
- func (i *Instance) CreateForkedInstanceWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
- func (i *Instance) CreateForkedOpenCodeInstance(newTitle, newGroupPath string) (*Instance, string, error)
- func (i *Instance) CreateForkedOpenCodeInstanceWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (*Instance, string, error)
- func (i *Instance) CreateForkedOpenCodeInstanceWithOptionsAndWorkDir(newTitle, newGroupPath string, opts *OpenCodeOptions, ...) (*Instance, string, error)
- func (i *Instance) CreateForkedPiInstance(newTitle, newGroupPath string) (*Instance, string, error)
- func (i *Instance) CreateForkedPiInstanceWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
- func (i *Instance) DetectCodexSession()
- func (i *Instance) DetectOpenCodeSession()
- func (inst *Instance) DisplayLastActivityTime() time.Time
- func (i *Instance) DisplaySessionID() string
- func (inst *Instance) EffectiveWorkingDir() string
- func (i *Instance) EnsurePluginsInstalled(sourceProfileDir string) error
- func (i *Instance) EnsureWorkerScratchConfigDir(sourceProfileDir string) (string, error)
- func (i *Instance) Exists() bool
- func (i *Instance) ForceNextStatusCheck()
- func (i *Instance) Fork(newTitle, newGroupPath string) (string, error)
- func (i *Instance) ForkOpenCode(newTitle, newGroupPath string) (string, error)
- func (i *Instance) ForkOpenCodeWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (string, error)
- func (i *Instance) ForkWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (string, error)
- func (i *Instance) GetActualWorkDir() string
- func (i *Instance) GetAutoName() bool
- func (i *Instance) GetAutoNameDescription() string
- func (i *Instance) GetClaudeOptions() *ClaudeOptions
- func (i *Instance) GetCodexOptions() *CodexOptions
- func (i *Instance) GetCopilotOptions() *CopilotOptions
- func (i *Instance) GetGenericSessionID() string
- func (i *Instance) GetHermesOptions() *HermesOptions
- func (i *Instance) GetHookStatus() (string, bool)
- func (i *Instance) GetJSONLPath() string
- func (i *Instance) GetJSONLPathChecked(peers []*Instance) (string, error)
- func (inst *Instance) GetLastActivityTime() time.Time
- func (i *Instance) GetLastResponse() (*ResponseOutput, error)
- func (i *Instance) GetLastResponseBestEffort() (*ResponseOutput, error)
- func (i *Instance) GetLastResponseBestEffortChecked(peers []*Instance) (*ResponseOutput, error)
- func (i *Instance) GetMCPInfo() *MCPInfo
- func (i *Instance) GetOpenCodeOptions() *OpenCodeOptions
- func (i *Instance) GetOpenCodePort() int
- func (i *Instance) GetSessionIDFromTmux() string
- func (inst *Instance) GetStatusThreadSafe() Status
- func (i *Instance) GetTmuxSession() *tmux.Session
- func (inst *Instance) GetToolThreadSafe() string
- func (inst *Instance) GetWaitingSince() time.Time
- func (i *Instance) HasUpdated() bool
- func (i *Instance) InvalidateProjectMCPIntegrationsCache()
- func (i *Instance) IsArchived() bool
- func (i *Instance) IsMaestro() bool
- func (inst *Instance) IsMultiRepo() bool
- func (inst *Instance) IsSSH() bool
- func (inst *Instance) IsSandboxed() bool
- func (inst *Instance) IsSubSession() bool
- func (inst *Instance) IsWorktree() bool
- func (i *Instance) Kill() error
- func (i *Instance) KillAndWait() error
- func (inst *Instance) LastObservedActivity() (time.Time, bool)
- func (i *Instance) LaunchModelID() string
- func (i *Instance) LaunchModelInfo() ModelInfo
- func (i *Instance) MCPGlobalConfigPath() string
- func (i *Instance) MCPInfoForLocalAttach() *MCPInfo
- func (i *Instance) MCPLocalConfigPath() string
- func (inst *Instance) MarkAccessed()
- func (i *Instance) NeedsWorkerScratchConfigDir() bool
- func (i *Instance) OpenContainerShell() (string, error)
- func (i *Instance) PostStartSync(maxWait time.Duration)
- func (i *Instance) Preview() (string, error)
- func (i *Instance) PreviewFull() (string, error)
- func (i *Instance) PreviewWindowFull(windowIndex int) (string, error)
- func (i *Instance) ReconcileTitleFromClaude(sessionID string) (string, bool)
- func (i *Instance) RefreshLiveSessionIDs()
- func (i *Instance) RegisterMCPChild(pid int)
- func (i *Instance) ResolveTitleFromClaude(sessionID string) (string, bool)
- func (i *Instance) Restart() error
- func (i *Instance) RestartFresh() error
- func (i *Instance) RestartWithEnv(env map[string]string) error
- func (i *Instance) SetAcknowledgedFromShared(ack bool)
- func (i *Instance) SetAutoName(autoName bool)
- func (i *Instance) SetAutoNameDescription(desc string)
- func (i *Instance) SetClaudeOptions(opts *ClaudeOptions) error
- func (i *Instance) SetCodexOptions(opts *CodexOptions) error
- func (i *Instance) SetGeminiModel(model string) error
- func (i *Instance) SetGeminiYoloMode(enabled bool)
- func (i *Instance) SetHermesOptions(opts *HermesOptions) error
- func (i *Instance) SetOpenCodeOptions(opts *OpenCodeOptions) error
- func (inst *Instance) SetParent(parentID string)
- func (inst *Instance) SetParentWithPath(parentID, parentProjectPath string)
- func (inst *Instance) SetStatusThreadSafe(s Status)
- func (i *Instance) SetTmuxSessionForTest(s *tmux.Session)
- func (inst *Instance) SetToolThreadSafe(t string)
- func (i *Instance) SpawnFailure() *SpawnFailureRecord
- func (i *Instance) Start() error
- func (i *Instance) StartWithMessage(message string) error
- func (i *Instance) StopServiceUnit() error
- func (i *Instance) Substate() Substate
- func (i *Instance) SupportsMCPAgentRestart() bool
- func (i *Instance) SyncSessionIDsFromTmux()
- func (i *Instance) SyncSessionIDsToTmux()
- func (i *Instance) SyncTmuxDisplayName()
- func (i *Instance) UnregisterMCPChild(pid int)
- func (i *Instance) UpdateClaudeSession(excludeIDs map[string]bool)
- func (i *Instance) UpdateCodexSession(excludeIDs map[string]bool)
- func (i *Instance) UpdateGeminiSession(excludeIDs map[string]bool)
- func (i *Instance) UpdateHookStatus(status *HookStatus)
- func (i *Instance) UpdateOpenCodeSSEStatus(status string, updatedAt time.Time)
- func (i *Instance) UpdateOpenCodeSession()
- func (i *Instance) UpdateStatus() error
- func (i *Instance) WaitForClaudeSession(maxWait time.Duration) string
- func (i *Instance) WaitForClaudeSessionWithExclude(maxWait time.Duration, excludeIDs map[string]bool) string
- func (i *Instance) WriteGlobalMCPConfig(names []string) error
- func (i *Instance) WriteLocalMCPConfig(names []string) error
- type InstanceData
- type InstanceSettings
- type Item
- type ItemType
- type LocalMCP
- type LogSettings
- type MCPDef
- type MCPInfo
- func GetCodexMCPInfo(codexHome string) *MCPInfo
- func GetCursorMCPInfo(projectPath string) *MCPInfo
- func GetGeminiMCPInfo(projectPath string) *MCPInfo
- func GetMCPInfo(projectPath string) *MCPInfo
- func GetOpenCodeMCPInfo(projectPath string) *MCPInfo
- func MCPInfoForLocalAttach(toolName, projectPath string) *MCPInfo
- type MCPMode
- type MCPPoolSettings
- type MCPServer
- type MCPServerConfig
- type MaintenanceResult
- type MaintenanceSettings
- type MatchRange
- type MaterializedProjectSkill
- type MigrationResult
- type ModelInfo
- type ModelPricing
- type MultiRepoWorktree
- type MultiRepoWorktreeResult
- type MutationError
- type NotificationEntry
- type NotificationManager
- func (nm *NotificationManager) Add(inst *Instance) error
- func (nm *NotificationManager) Clear()
- func (nm *NotificationManager) Count() int
- func (nm *NotificationManager) FormatBar() string
- func (nm *NotificationManager) GetEntries() []*NotificationEntry
- func (nm *NotificationManager) GetSessionByKey(key string) *NotificationEntry
- func (nm *NotificationManager) Has(sessionID string) bool
- func (nm *NotificationManager) IsMinimal() bool
- func (nm *NotificationManager) Remove(sessionID string)
- func (nm *NotificationManager) SyncFromInstances(instances []*Instance, currentSessionID string) (added, removed []string)
- type NotificationsConfig
- type OpenClawSettings
- type OpenCodeOptions
- type OpenCodeSSEStatus
- type OpenCodeSSEWatcher
- type OpenCodeSettings
- type PathCandidate
- type PathSource
- type PathSuggestInput
- type PerformanceSettings
- type PinMode
- type PluginDef
- type PreviewSettings
- type PricingOverride
- type PricingSettings
- type ProfileClaudeSettings
- type ProfileCodexSettings
- type ProfileCosts
- type ProfileMigrateOptions
- type ProfileMigrateResult
- func MigrateConductorToProfile(name, sourceProfile, targetProfile string, opts ProfileMigrateOptions) (*ProfileMigrateResult, error)
- func MigrateGroupToProfile(groupPath, sourceProfile, targetProfile string, opts ProfileMigrateOptions) (*ProfileMigrateResult, error)
- func MigrateSessionsToProfile(sourceProfile, targetProfile string, sessionIDs []string, ...) (*ProfileMigrateResult, error)
- type ProfileSettings
- type ProjectMCPSettings
- type ProjectSkillAttachment
- type ProjectSkillsManifest
- type Registry
- func (r *Registry) All() []ToolDef
- func (r *Registry) CustomNames() []string
- func (r *Registry) FilterActive() bool
- func (r *Registry) FilterFallback() bool
- func (r *Registry) FilterVisibleNames(names []string) []string
- func (r *Registry) Get(name string) *ToolDef
- func (r *Registry) GetCustom(name string) *ToolDef
- func (r *Registry) HiddenToolNames() []string
- func (r *Registry) IsBuiltin(name string) bool
- func (r *Registry) IsVisible(name string) bool
- func (r *Registry) Match(cmd string) string
- func (r *Registry) Visible() []ToolDef
- type RemoteConfig
- type RemoteKeySender
- type RemoteLatency
- type RemoteSessionInfo
- type ResolvedForkPlan
- type ResponseOutput
- type RevivalClass
- type ReviveBreaker
- type ReviveOutcome
- type Reviver
- type SSETarget
- type SSHRunner
- func (r *SSHRunner) Attach(sessionID string) error
- func (r *SSHRunner) CheckBinary(ctx context.Context) (version string, found bool)
- func (r *SSHRunner) CreateSession(ctx context.Context) (string, error)
- func (r *SSHRunner) CreateSessionWithOptions(ctx context.Context, tool, title, path, group string) (string, error)
- func (r *SSHRunner) DeleteSession(ctx context.Context, sessionID string) error
- func (r *SSHRunner) DeployBinary(ctx context.Context, binaryData []byte, remotePath string) error
- func (r *SSHRunner) DetectPlatform(ctx context.Context) (goos, goarch string, err error)
- func (r *SSHRunner) FetchCostSummary(ctx context.Context) (*costs.RemoteCostSummary, error)
- func (r *SSHRunner) FetchSessionOutput(ctx context.Context, sessionID string) (string, error)
- func (r *SSHRunner) FetchSessionPane(ctx context.Context, sessionID string) (string, error)
- func (r *SSHRunner) FetchSessions(ctx context.Context) ([]RemoteSessionInfo, error)
- func (r *SSHRunner) InstallBinary(ctx context.Context, binaryData []byte, expectedVersion string) error
- func (r *SSHRunner) MeasureLatency(ctx context.Context) (time.Duration, error)
- func (r *SSHRunner) OpenStream(ctx context.Context, args ...string) (io.WriteCloser, func() error, error)
- func (r *SSHRunner) ResolveRemotePath(ctx context.Context) string
- func (r *SSHRunner) RestartSession(ctx context.Context, sessionID string) error
- func (r *SSHRunner) Run(ctx context.Context, args ...string) ([]byte, error)
- func (r *SSHRunner) RunCommand(ctx context.Context, args ...string) ([]byte, error)
- func (r *SSHRunner) StopSession(ctx context.Context, sessionID string) error
- type SandboxConfig
- type SearchEntry
- type SearchResult
- type SearchTier
- type SelfHealSettings
- type SessionAnalytics
- type SessionBudget
- type SessionIDLifecycleEvent
- type SessionLifecycleEvent
- type ShellSettings
- type SkillCandidate
- type SkillSource
- type SkillSourceDef
- type SkillSourcesConfig
- type SlackSettings
- type SpawnAttempt
- type SpawnFailureRecord
- type Status
- type StatusEvent
- type StatusEventWatcher
- type StatusFileWatcher
- type StatusSettings
- type StopHookDecision
- type Storage
- func (s *Storage) Close() error
- func (s *Storage) DeleteGroupSubtree(path string) error
- func (s *Storage) DeleteInstance(id string) error
- func (s *Storage) GetDB() *statedb.StateDB
- func (s *Storage) GetFileMtime() (time.Time, error)
- func (s *Storage) GetUpdatedAt() (time.Time, error)
- func (s *Storage) InsertSessionAndVerify(newInstance *Instance, groupTree *GroupTree) error
- func (s *Storage) InstanceExists(id string) (bool, error)
- func (s *Storage) Load() ([]*Instance, error)
- func (s *Storage) LoadLite() ([]*InstanceData, []*GroupData, error)
- func (s *Storage) LoadRecentSessions() ([]*statedb.RecentSessionRow, error)
- func (s *Storage) LoadWithGroups() ([]*Instance, []*GroupData, error)
- func (s *Storage) Path() string
- func (s *Storage) PersistRevivedInstances(instances []*Instance) error
- func (s *Storage) Profile() string
- func (s *Storage) RemoveSessionAndVerify(id string, remainingInstances []*Instance, groupTree *GroupTree) error
- func (s *Storage) Save(instances []*Instance) error
- func (s *Storage) SaveGroupsOnly(groupTree *GroupTree) error
- func (s *Storage) SaveRecentSession(inst *Instance) error
- func (s *Storage) SaveWithGroups(instances []*Instance, groupTree *GroupTree) error
- func (s *Storage) SyncInstanceCwd(id, newCwd string) (bool, error)
- func (s *Storage) UpdateTitleIfUnlocked(id, title string) (applied bool, err error)
- func (s *Storage) WriteAutoNameDescription(id, description string) error
- type StorageData
- type StreamConfig
- type StreamEvent
- type StreamingRemoteKeySender
- type SubagentInfo
- type Substate
- type SystemStatsSettings
- type TelegramChannelEnablementResult
- type TelegramSettings
- type TelegramValidatorInput
- type TelegramWarning
- type TerminalSettings
- type TmuxSettings
- type ToolCall
- type ToolDef
- type ToolOptions
- type ToolOptionsWrapper
- type TransitionDaemon
- type TransitionNotificationEvent
- func DrainInboxForParent(parentID string) ([]TransitionNotificationEvent, error)
- func DrainStagePhaseForCrashTest(parentID string) ([]TransitionNotificationEvent, error)
- func ReadAndTruncateInbox(parentSessionID string) ([]TransitionNotificationEvent, error)
- func ReadDeadLetter(childSessionID string) ([]TransitionNotificationEvent, error)
- type TransitionNotifier
- func (n *TransitionNotifier) Close()
- func (n *TransitionNotifier) DeliverCompletion(rec CompletionRecord) bool
- func (n *TransitionNotifier) Flush()
- func (n *TransitionNotifier) NotifyFinished(event TransitionNotificationEvent) TransitionNotificationEvent
- func (n *TransitionNotifier) NotifyTransition(event TransitionNotificationEvent) TransitionNotificationEvent
- type UISettings
- func (u UISettings) GetAttachOnCreate() bool
- func (u UISettings) GetFooter() string
- func (u UISettings) GetITermOpenAs() string
- func (u UISettings) GetNewSessionEnterAdvances() bool
- func (u UISettings) GetPreviewOrientation() string
- func (u UISettings) GetPreviewPct() int
- func (u UISettings) GetRemoteLatencyRefreshSecs(fallbackSecs int) int
- func (u UISettings) GetRemoteSessionRefreshSecs() int
- func (u UISettings) GetShellSplit() string
- type UpdateSettings
- type UserConfig
- func (c *UserConfig) ClaimPollingEnabled() bool
- func (c *UserConfig) GetConductorClaudeCommand(name string) string
- func (c *UserConfig) GetConductorClaudeConfigDir(name string) string
- func (c *UserConfig) GetConductorClaudeEnv(name string) map[string]string
- func (c *UserConfig) GetConductorClaudeEnvFile(name string) string
- func (c *UserConfig) GetConductorClaudeMCPs(name string) []string
- func (c *UserConfig) GetConductorClaudeModel(name string) string
- func (c *UserConfig) GetConductorClaudePlugins(name string) []string
- func (c *UserConfig) GetConductorClaudeSkills(name string) []string
- func (c *UserConfig) GetConductorHermesEnvFile(name string) string
- func (c *UserConfig) GetGroupClaudeCommand(groupPath string) string
- func (c *UserConfig) GetGroupClaudeConfigDir(groupPath string) string
- func (c *UserConfig) GetGroupClaudeEnv(groupPath string) map[string]string
- func (c *UserConfig) GetGroupClaudeEnvFile(groupPath string) string
- func (c *UserConfig) GetGroupClaudeMCPs(groupPath string) []string
- func (c *UserConfig) GetGroupClaudeModel(groupPath string) string
- func (c *UserConfig) GetGroupClaudePlugins(groupPath string) []string
- func (c *UserConfig) GetGroupClaudeSkills(groupPath string) []string
- func (c *UserConfig) GetGroupHermesEnvFile(groupPath string) string
- func (c *UserConfig) GetGroupSort() string
- func (c *UserConfig) GetProfileClaudeConfigDir(profile string) string
- func (c *UserConfig) GetProfileCodexConfigDir(profile string) string
- func (c *UserConfig) GetShowAnalytics() bool
- func (c *UserConfig) GetShowNotes() bool
- func (c *UserConfig) GetShowOutput() bool
- func (c *UserConfig) GetSyncTitle() bool
- type WakeNudger
- type WatcherAlertsSettings
- type WatcherMeta
- type WatcherSettings
- type WebSettings
- type WorktreeSettings
Constants ¶
const ( ConductorAgentClaude = "claude" ConductorAgentCodex = "codex" ConductorAgentHermes = "hermes" ConductorSessionTitlePrefix = "conductor-" ConductorHeartbeatMessagePrefix = "Heartbeat:" ConductorBridgeHeartbeatPrefix = "[HEARTBEAT]" )
const ( // DefaultProfile is the name of the default profile DefaultProfile = "default" // ProfilesDirName is the directory containing all profiles ProfilesDirName = "profiles" // ConfigFileName is the global config file name ConfigFileName = "config.json" )
const ( SubstateNone = tmux.SubstateNone SubstateRunning = tmux.SubstateRunning SubstateIdleAtEmptyPrompt = tmux.SubstateIdleAtEmptyPrompt SubstateAuth401 = tmux.SubstateAuth401 )
const ( FieldTitle = "title" FieldPath = "path" FieldCommand = "command" FieldTool = "tool" FieldWrapper = "wrapper" FieldChannels = "channels" FieldPlugins = "plugins" FieldExtraArgs = "extra-args" FieldColor = "color" FieldNotes = "notes" FieldClaudeSessionID = "claude-session-id" FieldGeminiSessionID = "gemini-session-id" FieldOpenCodeSessionID = "opencode-session-id" FieldCodexSessionID = "codex-session-id" FieldTitleLocked = "title-locked" FieldNoTransitionNotify = "no-transition-notify" FieldSkipPermissions = "skip-permissions" FieldAutoMode = "auto-mode" FieldAccount = "account" // #924 per-session named account slot FieldIdleTimeout = "idle-timeout" // #1143 auto-stop dormant sessions FieldPin = "pin" // pin-sessions: anchor top/bottom of group // FieldModel persists the operator's selected per-session model (#1436, // follow-up to #1431). Tool-agnostic: routes to each tool's existing model // store (ClaudeOptions.Model, GeminiModel, OpenCodeOptions.Model, // CodexOptions.Model) via Instance.ApplyLaunchModel, so a model switched // after launch survives `session restart` instead of reverting to the // baked/default model. Restart-required (the running process keeps the // model it launched with). FieldModel = "model" )
Field names accepted by SetField. Kept as raw strings to match the `agent-deck session set <field>` CLI surface verbatim.
const ( // FutilityThreshold is how many consecutive futile revives trip the circuit. FutilityThreshold = 3 // InitialCooldown is the first throttle window after the circuit opens. InitialCooldown = 2 * time.Minute // MaxCooldown caps the exponential backoff. MaxCooldown = 30 * time.Minute // WedgeMinOpenCircuits is the number of simultaneously-open circuits that // looks server-wide (a wedge) rather than session-specific. WedgeMinOpenCircuits = 2 // WedgeWarnInterval rate-limits the loud wedge warning. WedgeWarnInterval = 10 * time.Minute // EntryTTL prunes stale per-session state (a session removed, renamed, or // long-healthy leaves no residue). EntryTTL = 1 * time.Hour )
Breaker tuning. Kept in one block so review can adjust N / backoff / TTL without hunting through the logic.
const ( MinPreviewPct = 10 MaxPreviewPct = 90 )
MinPreviewPct and MaxPreviewPct bound the preview width to keep both panes usable.
const ( ITermOpenAsTab = "tab" ITermOpenAsWindow = "window" DefaultITermOpenAs = ITermOpenAsTab )
iTerm "open as" modes for Shift+Enter dispatch.
const ( ShellSplitITerm = "iterm" ShellSplitTmux = "tmux" )
ShellSplit modes for the open_shell_here hotkey (issue #1470).
const ( PreviewOrientationRight = "right" PreviewOrientationBelow = "below" DefaultPreviewOrientation = PreviewOrientationRight )
Preview-pane orientation modes for wide terminals (>= 80 cols). "right" is the historical side-by-side split; "below" stacks the PREVIEW pane under the SESSIONS list (portrait-monitor friendly).
const ( // default preserves today's look; curated/compact/minimal are opt-in via // config.toml [ui] footer. DefaultFooter = FooterFull )
Footer hint-bar styles. See UISettings.Footer.
const ( DefaultRemoteSessionRefreshSecs = 15 MinRemoteSessionRefreshSecs = 5 MaxRemoteSessionRefreshSecs = 300 )
Remote session-list poll cadence bounds (issue #1170). The default is deliberately tighter than the historical hardcoded 30s so new remote sessions surface promptly; the min keeps a floor on SSH frequency.
const DefaultGroupName = "My Sessions"
DefaultGroupName is the display name for the default group where ungrouped sessions go
const DefaultGroupPath = "my-sessions"
DefaultGroupPath is the normalized path for the default group (used for lookups and protection)
const DefaultPreviewPct = 65
DefaultPreviewPct is the default preview-pane width percentage. Matches the historical hardcoded 0.35 sessions / 0.65 preview split.
const DefaultWorktreeSetupTimeout = 60 * time.Second
DefaultWorktreeSetupTimeout is the fallback used when no explicit value is configured. Kept small and visible so the git package can share it.
const DoneSentinelMarker = "===AGENTDECK_DONE==="
DoneSentinelMarker is the token a worker prints to assert that it has finished its assigned task. Issue #1186: the conductor previously had no trustworthy "worker finished" signal because Claude's Stop hook fires at the end of every turn and is mapped to the generic "waiting" status. Completion is now asserted by the only party that knows — the worker — by ending its final turn with a line of the form:
===AGENTDECK_DONE=== status=<ok|fail> summary=<text to end of line>
const FocusRequestKey = "focus_request"
FocusRequestKey is the metadata key the CLI writes and the TUI polls to drive session selection. The state.db is per-profile, so the key needs no profile suffix — the CLI and TUI operate on the same profile's db.
const FocusRequestTTL = 10 * time.Second
FocusRequestTTL bounds how long a focus request stays actionable. A TUI that starts minutes after a click must not jump on the stale request.
const GroupViewModeCount = 3
GroupViewModeCount is the number of cycle-able modes (used for "(mode+1)%N").
const LaunchdPlistName = "com.agentdeck.conductor-bridge"
LaunchdPlistName is the launchd label for the conductor bridge daemon
const MaestroSessionTitle = "conductor-maestro"
MaestroSessionTitle is the exact title of the fleet-supervisor session.
Maestro is the orchestrator-of-orchestrators: it sits one level above conductors, exactly as conductors sit above child sessions (see conductor/agent-deck/maestro-design.md). Phase-1 identification is by exact title — the same convention conductors used before graduating to the is_conductor column in the v4 schema migration. When the is_maestro column ships (Phase 2), IsMaestro is the single seam to swap.
const MaxStopHookBlocks = 3
MaxStopHookBlocks is the cap on consecutive stop-hook-induced blocks.
const MaxUnresolvedAttempts = 5
MaxUnresolvedAttempts bounds how many times the producer re-attempts an unresolvable target before the record is moved to the dead-letter store and the miss is logged ONCE. The old path logged dropped_no_target on every ~1s poll forever; this caps it (Temporal/DBOS/outbox all cap retries).
const ReasonIdleTimeoutExpired = "idle-timeout-expired"
const StreamSchemaVersion = "1"
StreamSchemaVersion identifies the event schema. Bump on any breaking change to field names or event shapes; consumers can branch on it.
const TierThresholdBalanced = 500 * 1024 * 1024
TierThresholdBalanced is the max size for balanced tier (500MB)
const TierThresholdInstant = 100 * 1024 * 1024
TierThresholdInstant is the max size for instant tier (100MB)
const TransitionNotifierLaunchdPlistName = "com.agentdeck.transition-notifier"
TransitionNotifierLaunchdPlistName is the launchd label for the transition notifier daemon.
const UnlimitedWorktreeSetupTimeout time.Duration = 0
UnlimitedWorktreeSetupTimeout is the sentinel returned by SetupTimeout() when the user has configured `setup_timeout_seconds = 0`. The git layer interprets this as "no deadline" (context.Background() instead of context.WithTimeout). Value chosen as 0 so the config value flows straight through to the git layer unchanged.
const UserConfigFileName = "config.toml"
UserConfigFileName is the TOML config file for user preferences
Variables ¶
var ( ErrSkillSourceExists = errors.New("skill source already exists") ErrSkillSourceNotFound = errors.New("skill source not found") ErrSkillNotFound = errors.New("skill not found") ErrSkillAmbiguous = errors.New("skill reference is ambiguous") ErrSkillUnsupportedKind = errors.New("skill is not a Claude-compatible directory skill") ErrSkillAlreadyAttached = errors.New("skill already attached") ErrSkillNotAttached = errors.New("skill not attached") ErrSkillTargetConflict = errors.New("skill target path conflict") )
var ErrGroupAlreadyExists = errors.New("group already exists at target path")
ErrGroupAlreadyExists is returned by RenameGroup when the target path collides with an existing group.
var ErrGroupNotFound = errors.New("group not found")
ErrGroupNotFound is returned by RenameGroup when oldPath does not resolve to an existing group.
var ErrInsertNotPersistent = errors.New("insert not persistent: row dropped by concurrent writer")
ErrInsertNotPersistent is returned by InsertSessionAndVerify when, after retries, the row is still missing from the database. The most likely cause is a concurrent SaveInstances rewrite from another agent-deck process that loaded the instances slice before this INSERT landed and then DELETE'd the row via the `DELETE FROM instances WHERE id NOT IN (...)` step inside SaveInstances.
Surfacing this as a real error (rather than silently returning success) is the user-facing half of the issue #1031 fix.
var ErrNoConversation = errors.New("no conversation file found")
ErrNoConversation reports that the session has no conversation file on disk yet (e.g. a fresh session that never exchanged a message). Callers switching accounts should treat this as non-fatal: there is simply nothing to migrate.
var ErrProfileMissing = errors.New("target profile does not exist")
ErrProfileMissing is returned when the destination profile has no state.db (i.e., it has never been used). Profiles are otherwise lazily created.
var ErrRefusingConfigSectionDrop = fmt.Errorf("session: refusing to save config.toml that would drop a populated [mcps] or [groups] section to empty (use SaveUserConfigWithIntent to intentionally clear)")
ErrRefusingConfigSectionDrop is returned by SaveUserConfig when the config it is asked to write would empty an entire top-level section ([mcps] or [groups]) that currently has entries on disk. These are the exact sections lost in the 2026-06-04 data-loss incident: a partially-constructed config saved over the live file silently dropped the whole MCP catalog and group overrides.
S3 data-loss safeguard: a save that zeroes a populated section is almost always a bug in the caller (it built a config without loading the existing one), not a deliberate "clear everything". Refuse it. A caller that genuinely means to clear all MCPs/groups must go through SaveUserConfigWithIntent with allowSectionDrop=true so the destructive intent is explicit and greppable.
var ErrRemovalNotPersistent = errors.New("removal not persistent: row resurrected by concurrent writer")
ErrRemovalNotPersistent is returned by RemoveSessionAndVerify when, after retries, the row is still observed in the database. The most likely cause is a concurrent SaveInstances rewrite from another agent-deck process that loaded the instances slice before this DELETE landed and re-inserted the row via INSERT OR REPLACE.
Surfacing this as a real error (rather than silently printing "✓ Removed") is the user-facing half of the issue #909 fix.
var ErrSameProfile = errors.New("source and target profile are the same")
ErrSameProfile is returned when source == target.
var ErrSessionRunning = errors.New("session is running; stop it first or pass --force")
ErrSessionRunning is returned when a session is in "running" status and Force is false. CLI handlers translate this to a non-zero exit with a hint.
var ErrStreamTimeout = errors.New("stream idle timeout")
ErrStreamTimeout is returned when IdleTimeout elapses with no progress.
var ValidDefaultFilters = map[string]bool{ "": true, "active": true, "running": true, "waiting": true, "idle": true, "error": true, }
ValidDefaultFilters lists acceptable values for DefaultFilter.
var ValidMutableFields = []string{ FieldTitle, FieldPath, FieldCommand, FieldTool, FieldWrapper, FieldChannels, FieldPlugins, FieldExtraArgs, FieldColor, FieldNotes, FieldClaudeSessionID, FieldGeminiSessionID, FieldOpenCodeSessionID, FieldCodexSessionID, FieldTitleLocked, FieldNoTransitionNotify, FieldSkipPermissions, FieldAutoMode, FieldAccount, FieldIdleTimeout, FieldPin, FieldModel, }
Functions ¶
func AckCompletion ¶ added in v1.9.43
AckCompletion marks a record delivered so replay never re-fires it.
func AddSkillSource ¶ added in v0.19.1
AddSkillSource adds a new named local source path.
func ApplyConfiguredLoadout ¶ added in v1.10.9
ApplyConfiguredLoadout materializes the declarative per-group / per-conductor skill, plugin, and MCP loadout ([groups.X.claude].skills/.plugins/.mcps and the conductor mirror) for a claude-compatible session, by driving the existing project-skills attach machinery and local .mcp.json writer — exactly as if the user had run `skill attach` / `mcp attach` by hand. Called at session create (add/launch) and re-asserted before every Start/StartWithMessage/Restart spawn, so a config edit takes effect on the next start and a healthy state is a cheap no-op.
Semantics (the loadout is an attach-only FLOOR):
- already attached (manifest entry + healthy target) → silent no-op
- manifest entry whose target went missing → re-materialized (heal)
- target exists as a real dir or foreign symlink (not manifest-managed) → skip + warning, never clobber; a human-placed dir beats config
- entry not resolvable in the skill-source registry → skip + warning
- removing an entry from the config list does NOT detach — subtraction is a deliberate `skill detach` (a config typo must not strip a live session's skills)
MCP entries are [mcps.X] catalog names appended to the session's local .mcp.json (never removed); unknown names skip + warn.
The effective lists are the union of the group ancestor chain and, for conductor sessions, the conductor block (group floor + conductor extras).
Returns the warnings (also slog-warned) so CLI call sites can print them; a nil return means nothing to do or everything healthy. Failures never block the spawn — the loadout is provisioning, not a launch gate.
func ApplyMultiRepoClaudeContext ¶ added in v1.9.31
func ApplyMultiRepoClaudeContext(tool string, multiRepoEnabled bool, claudeJSONPath, parentDir string, repoNames []string) error
ApplyMultiRepoClaudeContext is the single integration point called from home.go after creating a multi-repo parent dir. It pre-accepts the trust dialog, writes a parent .claude/CLAUDE.md describing the layout (with @path imports for child project instructions), and generates a .claude/settings.json with the intersection of allowed permissions across all child projects.
Only acts when tool == "claude" AND multiRepoEnabled is true. For any other combination it is a no-op, leaving claudeJSONPath untouched.
repoNames is the list of subdirectory names inside parentDir (one per repo). The caller is responsible for deriving them from the multi-repo worktree result (see DeduplicateDirnames + CreateMultiRepoWorktrees).
func ApplyMultiRepoCodexContext ¶ added in v1.10.9
ApplyMultiRepoCodexContext pre-accepts the Codex workspace-trust dialog for a multi-repo parent directory. Only acts when tool is Codex-compatible AND multiRepoEnabled is true. For sandboxed multi-repo sessions, trust for /workspace is seeded separately during sandbox config sync.
func ApplyProjectSkills ¶ added in v0.19.1
func ApplyProjectSkills(projectPath, tool string, desired []SkillCandidate) error
ApplyProjectSkills makes project attachments exactly match desired candidates. This is useful for TUI apply flows where users move items between columns.
func ArchiveTimeUTC ¶ added in v1.9.55
ArchiveTimeUTC returns the archive timestamp in UTC, or zero when not archived.
func BridgeDaemonHint ¶ added in v0.16.0
func BridgeDaemonHint() string
BridgeDaemonHint returns a platform-appropriate hint for starting the bridge daemon.
func CheckClaudeHooksInstalled ¶ added in v0.16.0
CheckClaudeHooksInstalled checks if agent-deck hooks are present in settings.json.
func CheckCursorHooksInstalled ¶ added in v1.9.70
CheckCursorHooksInstalled reports whether required agent-deck Cursor hooks are installed.
func CheckGeminiHooksInstalled ¶ added in v0.25.0
CheckGeminiHooksInstalled checks whether required agent-deck Gemini hooks are installed.
func CheckHermesHooksInstalled ¶ added in v1.9.47
CheckHermesHooksInstalled returns true if all agent-deck hook entries are present in Hermes's config.yaml.
func ChildLaunchEnv ¶ added in v1.9.31
ChildLaunchEnv returns the environment for a child claude process, guaranteed NOT to inherit the conductor's telegram pollution: every TELEGRAM_* var and any inherited CLAUDE_CONFIG_DIR are stripped, and the child's own config dir is pinned when childConfigDir is non-empty.
Issue #1163: all child-claude spawn paths in internal/session and cmd/agent-deck/launch*.go MUST use this — raw os.Environ() is forbidden by the forbidigo lint rule in .golangci.yml so a future caller cannot reintroduce the CLAUDE_CONFIG_DIR leak (EVIDENCE-env.md). inst is accepted for call-site clarity and future per-instance policy.
func ClaudeSessionName ¶ added in v1.9.48
ClaudeSessionName resolves the user's ~/.claude and returns the Claude session name for sessionID. Empty string on any error or no match.
func ClaudeSessionNameIn ¶ added in v1.9.48
ClaudeSessionNameIn scans claudeDir/sessions/*.json and returns the trimmed `name` of the entry whose sessionId matches. Empty string when there's no match, no name, or the sessions dir is unreadable.
The files are per-PID, so a resumed session can match several entries — the live process plus stale files left by earlier runs. The freshest entry (by updatedAt, falling back to file mtime) is authoritative, even when its name is empty: returning a stale file's old name would re-sync a title the user has since changed or cleared.
Issue #572: Claude Code writes per-process metadata here when the user starts with `claude --name X` or runs `/rename X` mid-session. claudeDir is an explicit parameter so tests can point it at a temp dir.
Claude Code 2.1.19x also auto-derives a name from the cwd folder and stamps nameSource="derived". That is not a user rename, so a derived name is treated as no name at all — including on the freshest entry, where it suppresses any stale user name (mirrors the freshest-unnamed rule). A name with no nameSource (older Claude) is always a user rename, so it is honored.
func CleanStaleEventFiles ¶ added in v0.18.0
func CleanStaleEventFiles()
CleanStaleEventFiles removes event files older than 24 hours.
func CleanStaleSSHSockets ¶ added in v1.9.60
func CleanStaleSSHSockets()
CleanStaleSSHSockets removes orphaned SSH ControlMaster sockets from sshControlDir (#1421). When an SSH master process dies unexpectedly (remote agent-deck update restarts sshd, network drop, remote reboot), its ControlPath socket file is left behind on disk with no process listening on it. Because agent-deck uses ControlMaster=auto, the NEXT ssh invocation tries to reuse that stale socket and hangs indefinitely — ConnectTimeout only bounds the initial TCP dial, NOT the Unix-domain-socket connect to the mux. The result: fetchRemoteSessions (and `agent-deck remote sessions`) block forever and every remote session disappears from the TUI until restart.
The cleanup probes each socket with a short net.DialTimeout. A live master answers the connect immediately; a stale socket cannot be connected to (connection refused — the listener is gone), so it is removed. Removing a stale socket is safe: the next ssh invocation simply opens a fresh master.
Best-effort and fully defensive: an unreadable directory, a transient stat error, or a failed remove is swallowed (logged at debug), never fatal — leaving a socket in place is strictly better than blocking the caller. Non-socket files in the directory are ignored.
func ClearAllCodexMCPInfoCache ¶ added in v1.10.9
func ClearAllCodexMCPInfoCache()
ClearAllCodexMCPInfoCache clears all Codex MCP cache entries.
func ClearAllCursorMCPInfoCache ¶ added in v1.9.47
func ClearAllCursorMCPInfoCache()
ClearAllCursorMCPInfoCache clears all Cursor MCP cache entries (needed after global ~/.cursor/mcp.json writes).
func ClearAllOpenCodeMCPInfoCache ¶ added in v1.9.59
func ClearAllOpenCodeMCPInfoCache()
ClearAllOpenCodeMCPInfoCache clears all OpenCode MCP cache entries (needed after global writes).
func ClearCodexMCPCache ¶ added in v1.10.9
func ClearCodexMCPCache(codexHome string)
ClearCodexMCPCache invalidates cached Codex MCP info for a Codex home.
func ClearCursorMCPCache ¶ added in v1.9.47
func ClearCursorMCPCache(projectPath string)
ClearCursorMCPCache invalidates cached Cursor MCP info for a project path.
func ClearFocusRequest ¶ added in v1.10.9
ClearFocusRequest consumes the request (consume-once). statedb has no DeleteMeta, so an empty value is the documented "no request" sentinel.
func ClearHookSessionAnchor ¶ added in v0.25.0
func ClearHookSessionAnchor(instanceID string)
ClearHookSessionAnchor removes the persisted hook session ID sidecar.
func ClearMCPCache ¶ added in v0.5.3
func ClearMCPCache(projectPath string)
ClearMCPCache invalidates the MCP cache for a project path and all parent directories This is important because getMCPInfoUncached walks up parent directories to find .mcp.json
func ClearOpenCodeMCPCache ¶ added in v1.9.59
func ClearOpenCodeMCPCache(projectPath string)
ClearOpenCodeMCPCache invalidates cached OpenCode MCP info for a project path.
func ClearProjectMCPs ¶ added in v0.5.3
ClearProjectMCPs removes all MCPs from projects[path].mcpServers in Claude's config
func ClearUserConfigCache ¶ added in v0.8.12
func ClearUserConfigCache()
ClearUserConfigCache clears the cached user config, allowing tests to reset state. This does NOT reload - the next LoadUserConfig() call will read fresh from disk. Resets both the cache pointer AND the tracked mtime so the invalidation state machine starts clean.
func CommitToInbox ¶ added in v1.9.44
func CommitToInbox(parentSessionID string, event TransitionNotificationEvent) error
CommitToInbox writes one completion record to the parent's durable inbox with LAST-WINS-PER-CHILD semantics: any existing unacked record for the same child is dropped first, so there is at most ONE pending record per child (issue #1225 — kills flood at the source; the old path appended one line per busy retry). The write is atomic (temp file + rename via rewriteInboxLocked, then a single append under the same lock). Stamps TurnFingerprint when absent.
This is the unified producer entry point for both the interactive (running→waiting) and one-shot (run-task kernel-exit) paths.
func CompletionRecordExists ¶ added in v1.9.43
CompletionRecordExists reports whether a wrapper has claimed/finished the given child under the given profile. The daemon uses it to stand down from poll-inference: if the kernel-exit path owns this child, the Stop-hook path must not also fire.
func ConductorDefaultDir ¶ added in v1.9.63
ConductorDefaultDir returns the default conductor base directory, IGNORING any [conductor].dir override. ConductorDir() consults the override first; this resolves only the underlying <data-dir>/conductor (XDG with legacy ~/.agent-deck/conductor fallback). migrate-dir and the split-brain detector need the pre-override location to find homes that did not move when the key flipped.
func ConductorDir ¶ added in v0.12.0
ConductorDir returns the base conductor directory. When [conductor].dir is set in config.toml it takes precedence (tilde and $VAR expanded); empty falls through to the default <data-dir>/conductor resolution (XDG with legacy ~/.agent-deck/conductor fallback).
func ConductorNameDir ¶ added in v0.12.0
ConductorNameDir returns the directory for a named conductor (~/.agent-deck/conductor/<name>)
func ConductorProfileDir ¶ added in v0.12.0
ConductorProfileDir returns the per-profile conductor directory. Deprecated: Use ConductorNameDir instead. Kept for backward compatibility.
func ConductorSessionTitle ¶ added in v0.12.0
ConductorSessionTitle returns the session title for a named conductor
func ConductorSystemActive ¶ added in v1.9.64
func ConductorSystemActive() bool
ConductorSystemActive reports whether the conductor system is active by deriving the answer from filesystem/registry state instead of a config flag. The system is "active" exactly when at least one conductor exists on disk (a directory under ConductorDir containing a valid meta.json).
This replaces the former [conductor].enabled config flag (issue #1361), which was write-once-true and whose only reachable "off" value silently broke the heartbeat. Deriving from presence means enabled-state cannot drift from reality: it is true iff there is something to conduct.
func ConfiguredHiddenToolNames ¶ added in v1.9.54
func ConfiguredHiddenToolNames() []string
ConfiguredHiddenToolNames returns the [ui].hidden_tools denylist from config.
func ConsumedTurnsDir ¶ added in v1.9.44
func ConsumedTurnsDir() string
ConsumedTurnsDir holds per-parent consumed-fingerprint ledgers.
func ConvertToClaudeDirName ¶ added in v0.8.49
ConvertToClaudeDirName converts a filesystem path to Claude's directory naming format. Claude Code replaces all non-alphanumeric characters (except hyphens) with hyphens. Example: /Users/master/Code cloud/!Project → -Users-master-Code-cloud--Project
func CountRunningInGroup ¶ added in v1.9.1
CountRunningInGroup returns the number of instances in the given group whose status is StatusRunning. Queued, stopped, and other-group instances are not counted.
func CreateExampleConfig ¶ added in v0.3.0
func CreateExampleConfig() error
CreateExampleConfig creates an example config file if none exists
func CreateProfile ¶ added in v0.3.0
CreateProfile creates a new empty profile
func DeadLetterDir ¶ added in v1.9.44
func DeadLetterDir() string
DeadLetterDir returns the directory holding dead-lettered inbox records.
func DeadLetterPathFor ¶ added in v1.9.44
DeadLetterPathFor returns the dead-letter JSONL path for a child.
func DecodeFocusRequest ¶ added in v1.10.9
DecodeFocusRequest parses a stored payload. fresh is true only when the payload is well-formed, has a non-empty id, and ts is within ttl of nowNano. A stale-but-parseable payload returns its id with fresh=false so the caller can still log/clear it.
func DecodeFocusRequestAttach ¶ added in v1.10.9
func DecodeFocusRequestAttach(val string, nowNano int64, ttl time.Duration) (id string, attach bool, fresh bool)
DecodeFocusRequestAttach parses a stored payload and additionally reports whether the request asked to attach the session. attach is only meaningful when fresh is true.
func DeduplicateDirnames ¶ added in v0.26.2
DeduplicateDirnames returns unique directory names for the given paths. When multiple paths share the same basename, a numeric suffix is appended (e.g., "src-1").
func DeleteProfile ¶ added in v0.3.0
DeleteProfile deletes a profile and all its data
func DetectConductorDirSplitBrain ¶ added in v1.9.63
DetectConductorDirSplitBrain reports the split-brain condition introduced by a declarative [conductor].dir flip: the key resolves immediately, but a populated fleet's physical homes do not move with it. It fires ONLY when the resolved conductor dir is empty AND the pre-override default base still holds conductor homes, returning a one-line warning pointing at migrate-dir.
Detection-only, no side effects (mirrors HeartbeatDaemonStale). Returns ("", false) when there is no override in play, the resolved dir is already populated, or the default base is empty.
func DiscoverHermesGatewayURL ¶ added in v1.9.47
func DiscoverHermesGatewayURL() string
DiscoverHermesGatewayURL auto-detects the hermes gateway URL. It checks gateway_state.json first (cheap), then probes the well-known local address. Returns "" if the gateway does not appear to be reachable.
func EmitTelegramChannelDriftWarning ¶ added in v1.9.27
func EmitTelegramChannelDriftWarning(title, instanceID, configDir string, channels []string, result TelegramChannelEnablementResult)
EmitTelegramChannelDriftWarning logs a WARN-level structured entry when VerifyTelegramChannelEnabled returns OK=false for a session that owns a telegram channel. The code field is stable (`telegram_channel_plugin_drift`) so log-monitoring rules can trigger on it without parsing free-form text.
Intentionally a no-op when result.OK is true — callers can invoke it unconditionally on the prepare path.
func EncodeFocusRequest ¶ added in v1.10.9
EncodeFocusRequest serializes a select-only focus request payload.
func EncodeFocusRequestAttach ¶ added in v1.10.9
EncodeFocusRequestAttach serializes a focus request, optionally asking the TUI to attach the session rather than just selecting it.
func EventFingerprint ¶ added in v1.7.74
func EventFingerprint(e TransitionNotificationEvent) string
EventFingerprint returns a stable identifier for a transition event, keyed on its intrinsic identity rather than the moment of any particular emit. Two attempts to persist the "same" logical transition (same child, same status flip, same observed timestamp) collapse to the same fingerprint and are deduplicated by the inbox writer and the notifier-missed log.
Keying on Timestamp.UnixNano() is load-bearing: the daemon stamps the event once when it first observes the flip and that same TransitionNotificationEvent is reused for the producer commit, so the timestamp is stable across re-observations. time.Now() at write-emit time would NOT be stable and would silently break dedup.
The fingerprint is a hex SHA-256 so it can safely be embedded in a JSON string field without escaping concerns and is cheap to grep for.
func ExpandPath ¶ added in v0.19.6
ExpandPath expands environment variables and ~ prefix in a path. Use resolvePath when relative paths also need to be resolved against a working directory.
func FilterVisibleToolNames ¶ added in v1.9.48
FilterVisibleToolNames removes tools hidden by show_only_installed_tools from names. The empty command "" (shell) is always kept. With the flag off it returns names unchanged (byte-identical default behavior).
func FindAgentDeck ¶ added in v1.5.1
func FindAgentDeck() string
FindAgentDeck looks for agent-deck in common locations
func ForgetConsumedTurnsForChild ¶ added in v1.9.44
func ForgetConsumedTurnsForChild(childSessionID string)
ForgetConsumedTurnsForChild removes any consumed-turn ledger entries for a child across all parents — used by rm_sweep on child removal so the ledger doesn't leak. Best-effort.
func FormatCompletionsForInjection ¶ added in v1.9.44
func FormatCompletionsForInjection(events []TransitionNotificationEvent) string
FormatCompletionsForInjection renders drained completions as the human- readable reason injected into the conductor's next turn.
func GenerateHeartbeatPlist ¶ added in v0.13.0
GenerateHeartbeatPlist returns a launchd plist for a conductor's heartbeat timer
func GenerateID ¶ added in v1.3.1
func GenerateID() string
generateID generates a unique session ID GenerateID creates a unique session identifier.
func GenerateLaunchdPlist ¶ added in v0.12.0
GenerateLaunchdPlist returns a launchd plist with paths substituted
func GenerateSessionName ¶ added in v0.13.0
func GenerateSessionName() string
GenerateSessionName returns a random "adjective-noun" name.
func GenerateSystemdBridgeService ¶ added in v0.16.0
GenerateSystemdBridgeService returns a systemd unit for the bridge daemon
func GenerateSystemdHeartbeatService ¶ added in v0.16.0
GenerateSystemdHeartbeatService returns a systemd service unit for a conductor heartbeat
func GenerateSystemdHeartbeatTimer ¶ added in v0.16.0
GenerateSystemdHeartbeatTimer returns a systemd timer unit for a conductor heartbeat
func GenerateSystemdTransitionNotifierService ¶ added in v0.19.13
GenerateSystemdTransitionNotifierService returns the systemd unit content for transition notifier.
func GenerateTransitionNotifierLaunchdPlist ¶ added in v0.19.13
GenerateTransitionNotifierLaunchdPlist returns a launchd plist for the transition notifier daemon.
func GenerateUniqueSessionName ¶ added in v0.13.0
GenerateUniqueSessionName generates a name that doesn't collide with existing session titles in the given group. Retries up to 10 times, then falls back to appending a timestamp.
func GetAgentDeckDir ¶ added in v0.3.0
GetAgentDeckDir returns the effective agent-deck data directory. It is a broad compatibility wrapper for data/runtime callers, not the config root. Profile/session migrations must use profileDataRootDir().
func GetAvailableGeminiModels ¶ added in v0.8.79
GetAvailableGeminiModels returns a sorted list of Gemini models that support generateContent. Priority: 1) GEMINI_MODELS_OVERRIDE env var, 2) cached API result, 3) live API call, 4) fallback list.
func GetAvailableMCPNames ¶ added in v0.5.3
func GetAvailableMCPNames() []string
GetAvailableMCPNames returns sorted list of MCP names from config.toml
func GetAvailableMCPs ¶ added in v0.5.3
GetAvailableMCPs returns MCPs from config.toml as a map This replaces the old catalog-based approach with explicit user configuration
func GetAvailablePluginNames ¶ added in v1.9.2
func GetAvailablePluginNames() []string
GetAvailablePluginNames returns sorted catalog keys of plugins. Refused entries are excluded (consistent with GetAvailablePlugins).
func GetAvailablePlugins ¶ added in v1.9.2
GetAvailablePlugins returns the plugin catalog from config.toml, never nil. Filters out:
- entries refused by IsTelegramOfficialRefusal (RFC §6)
- entries failing validatePluginDef (RFC charset filter — security defense against path traversal, argv injection, lock-path escape)
Invalid entries are logged once per LoadUserConfig cycle and silently dropped — callers never see them, so unsafe values cannot reach exec, filesystem ops, or settings.json mutations.
func GetClaudeCommand ¶ added in v0.8.30
func GetClaudeCommand() string
GetClaudeCommand returns the configured Claude command/alias Priority: 1) UserConfig setting, 2) Default "claude" This allows users to configure an alias like "cdw" or "cdp" that sets CLAUDE_CONFIG_DIR automatically, avoiding the need for config_dir setting
func GetClaudeCommandForInstance ¶ added in v1.10.9
GetClaudeCommandForInstance returns the Claude command for this Instance, extending GetClaudeCommand with the per-conductor / per-group levels. Priority (most-specific → least-specific, same order as the config_dir chain, CFG-08 mirror semantics):
- [conductors.<name>.claude].command — conductor sessions only
- [groups."<group>".claude].command — ancestor-walking
- [claude].command (global)
- "claude"
An instance-level custom command (a non-"claude" command string stored on the session, e.g. from `add -c <wrapper>`) never reaches this resolver — the spawn builders only consult it when the stored command is the default "claude", so the explicit per-session command stays the strongest level.
func GetClaudeConfigDir ¶ added in v0.4.1
func GetClaudeConfigDir() string
GetClaudeConfigDir returns the Claude config directory for the active profile.
func GetClaudeConfigDirForGroup ¶ added in v1.5.4
GetClaudeConfigDirForGroup returns the Claude config directory, walking the group chain: env > group > profile > global > default.
func GetClaudeConfigDirForInstance ¶ added in v1.5.4
GetClaudeConfigDirForInstance returns the Claude config directory for this Instance. Per-instance TOML overrides (conductor, group) beat the shell-wide CLAUDE_CONFIG_DIR env var; less-specific config (profile, global) falls to env and below.
Priority (most-specific → least-specific):
- Instance.Account (#924) → [profiles.<account>.claude].config_dir
- [conductors.<name>.claude].config_dir — consulted only when Instance.Title starts with "conductor-"
- [groups."<group>".claude].config_dir
- CLAUDE_CONFIG_DIR env var
- [profiles.<profile>.claude].config_dir
- [claude].config_dir
- ~/.claude
Why conductor/group beat env (fix-config-dir-priority, 2026-04-17): developer shells commonly export CLAUDE_CONFIG_DIR via aliases (cdp, cdw) to select a profile. When the user then writes an explicit [conductors.foo.claude] or [groups.bar.claude] block in config.toml, that TOML block is scoped to exactly that conductor/group and is semantically MORE specific than a shell-wide default. The old env-first order silently shadowed every TOML override. Profile/global remain beaten by env because they're shell-wide too (less specific than env in intent).
func GetClaudeConfigDirSourceForGroup ¶ added in v1.5.4
GetClaudeConfigDirSourceForGroup returns (path, source) for the group chain. Sources: "env", "group", "profile", "global", "default".
func GetClaudeConfigDirSourceForInstance ¶ added in v1.5.4
GetClaudeConfigDirSourceForInstance returns (path, source) for the instance chain. Source labels: "account" (issue #924), "conductor", "group", "env", "profile", "global", "default".
func GetClaudeSessionID ¶ added in v0.4.1
GetClaudeSessionID returns the ACTIVE session ID for a project path It first tries to find the currently running session by checking recently modified .jsonl files, then falls back to lastSessionId from config
func GetCodexCommand ¶ added in v1.9.2
func GetCodexCommand() string
GetCodexCommand returns the configured Codex command/alias.
func GetCodexConfigDir ¶ added in v1.10.9
func GetCodexConfigDir() string
GetCodexConfigDir returns the Codex home directory used for config.toml.
func GetCodexConfigPath ¶ added in v1.10.9
GetCodexConfigPath returns the path to Codex's user-level config.toml under codexHome.
func GetConductorLastActivity ¶ added in v1.9.20
GetConductorLastActivity returns the most recent persistent agent activity across sessions watched by the conductor. Conductors watch sessions in their profile, including sessions that are not parented under the conductor, so the activity scope includes every non-conductor session in the profile plus any conductor descendants. The conductor's own session is intentionally excluded so that heartbeat responses written to it do not reset the idle timer.
Returns zero time (and no error) when the conductor has no managed sessions; callers decide whether zero means "no data" or "idle".
func GetConfigPath ¶ added in v0.3.0
GetConfigPath returns the path to the global config file
func GetCrushCommand ¶ added in v1.9.14
func GetCrushCommand() string
GetCrushCommand returns the configured crush command/alias. Mirrors GetCodexCommand on main: prefer the user config override, fall back to the bare binary name.
func GetCursorConfigDir ¶ added in v1.9.47
func GetCursorConfigDir() string
GetCursorConfigDir returns ~/.cursor (Cursor IDE / Agent CLI config).
func GetCustomToolNames ¶ added in v0.8.87
func GetCustomToolNames() []string
GetCustomToolNames returns sorted custom tool names from config.toml, excluding names that shadow built-in tools (claude, gemini, opencode, codex, pi, shell, cursor, aider). Returns nil if no custom tools are configured.
func GetDBPathForProfile ¶ added in v0.11.0
GetDBPathForProfile returns the path to the state.db file for a specific profile.
func GetDefaultTool ¶ added in v0.4.3
func GetDefaultTool() string
GetDefaultTool returns the user's preferred default tool for new sessions Returns empty string if not configured (defaults to shell)
func GetDirectoryCompletions ¶ added in v0.8.86
GetDirectoryCompletions returns a list of directories that match the input prefix. Supports absolute, relative, and tilde-prefixed (~) paths.
func GetEffectiveProfile ¶ added in v0.3.0
GetEffectiveProfile returns the profile to use, considering: 1. Explicitly provided profile (from -p flag) 2. Environment variable AGENTDECK_PROFILE 3. Inferred from CLAUDE_CONFIG_DIR (e.g. ~/.claude-work -> "work") 4. Config default profile 5. Fallback to "default"
Priority 3 was added to fix issue #881: prior to this, the TUI's profile.DetectCurrentProfile honored CLAUDE_CONFIG_DIR while the web / storage / push paths did not, so the same user on the same machine could see different sessions in TUI vs web. Both call sites now route through this function to guarantee a single source of truth.
func GetEventsDir ¶ added in v0.18.0
func GetEventsDir() string
GetEventsDir returns the path to the events directory.
func GetGeminiConfigDir ¶ added in v0.8.0
func GetGeminiConfigDir() string
GetGeminiConfigDir returns ~/.gemini Unlike Claude, Gemini has no GEMINI_CONFIG_DIR env var override
func GetGeminiMCPNames ¶ added in v0.8.1
func GetGeminiMCPNames() []string
GetGeminiMCPNames returns names of configured MCPs from settings.json
func GetGeminiSessionsDir ¶ added in v0.8.0
GetGeminiSessionsDir returns the chats directory for a project Format: ~/.gemini/tmp/<project_hash>/chats/
func GetGlobalHTTPPool ¶ added in v0.8.96
GetGlobalHTTPPool returns the global HTTP pool instance (may be nil)
func GetGlobalMCPNames ¶ added in v0.5.3
func GetGlobalMCPNames() []string
GetGlobalMCPNames returns the names of MCPs currently in Claude's global config
func GetGlobalPool ¶ added in v0.6.2
GetGlobalPool returns the global socket pool instance (may be nil if disabled)
func GetGlobalPoolRunningCount ¶ added in v0.8.70
func GetGlobalPoolRunningCount() int
GetGlobalPoolRunningCount returns the number of running MCPs in the global pool
func GetGroupLevel ¶
GetGroupLevel returns the nesting level of a group (0 for root, 1 for child, etc.)
func GetHTTPServerStatus ¶ added in v0.8.96
GetHTTPServerStatus returns the status of an HTTP MCP server
func GetHermesConfigDir ¶ added in v1.9.47
func GetHermesConfigDir() string
GetHermesConfigDir returns the Hermes config directory (~/.hermes).
func GetHermesGatewayURL ¶ added in v1.9.47
func GetHermesGatewayURL() string
GetHermesGatewayURL returns the hermes gateway URL. It first checks the explicit gateway_url in agent-deck's config; if unset, it attempts auto-discovery via DiscoverHermesGatewayURL so users who run the hermes gateway get session health detection without any manual configuration.
func GetHooksDir ¶ added in v0.16.0
func GetHooksDir() string
GetHooksDir returns the path to the hooks status directory.
func GetHotkeyOverrides ¶ added in v0.21.0
GetHotkeyOverrides returns user-configured hotkey overrides from config.toml.
Merge order (issue #434):
- Start from the `[hotkeys]` table.
- If `[tmux].detach_key` is set AND the caller has not already set `[hotkeys].detach`, layer tmux.detach_key into the hotkeys map as the "detach" action. Explicit `[hotkeys].detach` always wins so there is exactly one authoritative source of truth when both are present.
Returns nil only when nothing is configured in either table.
func GetMCPDefaultScope ¶ added in v0.10.11
func GetMCPDefaultScope() string
GetMCPDefaultScope returns the configured default MCP scope. Returns "local", "global", or "user". Defaults to "local" if unset or invalid.
func GetManageMCPJson ¶ added in v0.19.6
func GetManageMCPJson() bool
GetManageMCPJson returns whether agent-deck should write to .mcp.json files. Defaults to true when unset.
func GetOpenCodeConfigDir ¶ added in v1.9.59
func GetOpenCodeConfigDir() string
GetOpenCodeConfigDir returns ~/.config/opencode (OpenCode global config directory).
func GetProfileDir ¶ added in v0.3.0
GetProfileDir returns the path to a specific profile's directory
func GetProfilesDir ¶ added in v0.3.0
GetProfilesDir returns the path to the profiles directory
func GetProjectMCPNames ¶ added in v0.5.3
GetProjectMCPNames returns MCPs from projects[path].mcpServers in Claude's config
func GetProjectSkillsDir ¶ added in v1.7.32
GetProjectSkillsDir returns the runtime-managed project skill directory.
func GetProjectSkillsManifestPath ¶ added in v0.19.1
GetProjectSkillsManifestPath returns <project>/.agent-deck/skills.toml.
func GetProjectSkillsPath ¶ added in v1.7.32
GetProjectSkillsPath returns the runtime-specific project skills path.
func GetSessionIDLifecycleLogPath ¶ added in v0.28.0
func GetSessionIDLifecycleLogPath() string
GetSessionIDLifecycleLogPath returns ~/.agent-deck/logs/session-id-lifecycle.jsonl.
func GetSessionLifecycleLogPath ¶ added in v1.9.29
func GetSessionLifecycleLogPath() string
GetSessionLifecycleLogPath returns ~/.agent-deck/logs/session-lifecycle.jsonl. Distinct from session-id-lifecycle.jsonl (which logs bind/rebind), this covers process-level lifecycle decisions like idle-timeout-expired.
func GetSkillPoolPath ¶ added in v0.19.1
GetSkillPoolPath returns the managed skill pool path.
func GetSkillSourcesPath ¶ added in v0.19.1
GetSkillSourcesPath returns the skill source registry path.
func GetSkillsRootPath ¶ added in v0.19.1
GetSkillsRootPath returns the Agent Deck skills config directory, falling back to the legacy ~/.agent-deck/skills tree when it already exists.
func GetTheme ¶ added in v0.8.16
func GetTheme() string
GetTheme returns the current theme, defaulting to "dark"
func GetToolBusyPatterns ¶ added in v0.3.0
GetToolBusyPatterns returns busy patterns for a tool (custom + built-in)
func GetToolCommand ¶ added in v1.9.19
GetToolCommand returns the configured command override for a builtin tool, falling back to the bare tool name if no override is set.
func GetToolIcon ¶ added in v0.3.0
GetToolIcon returns the icon for a tool (custom or built-in)
func GetUserConfigPath ¶ added in v0.3.0
GetUserConfigPath returns the path to the user config file
func GetUserMCPNames ¶ added in v0.8.69
func GetUserMCPNames() []string
GetUserMCPNames returns the names of MCPs in ~/.claude.json (ROOT config) These MCPs are loaded by ALL Claude sessions regardless of CLAUDE_CONFIG_DIR. This is different from GetGlobalMCPNames which reads from $CLAUDE_CONFIG_DIR/.claude.json
func GetUserMCPRootPath ¶ added in v0.8.69
func GetUserMCPRootPath() string
GetUserMCPRootPath returns the path to ~/.claude.json (ROOT config, always read by Claude) This is the ROOT config that Claude ALWAYS reads, regardless of CLAUDE_CONFIG_DIR setting. MCPs defined here apply to ALL Claude sessions globally.
func GetWebMutationsEnabled ¶ added in v1.7.75
func GetWebMutationsEnabled() bool
GetWebMutationsEnabled returns whether `agent-deck web` should accept mutating HTTP requests (POST/PATCH/DELETE). Defaults to true when the `[web].mutations_enabled` key is omitted from config.toml.
func GroupByProject ¶
GroupByProject groups sessions by their parent project directory
func GroupMaxConcurrent ¶ added in v1.9.1
GroupMaxConcurrent returns the effective max_concurrent cap for groupPath by looking it up in the group tree. Returns 0 (unlimited) when the group is not found, preserving legacy behavior for groups without persisted metadata.
func GroupPathForProject ¶ added in v1.9.8
GroupPathForProject is the exported wrapper around extractGroupPath. It gives CLI callers (issue #972) a single source of truth for "what group does this project path imply" — matching what NewInstance assigns by default — so launch/add can prefer cwd-derived groups over inherited parent groups without duplicating the heuristic.
func HashProjectPath ¶ added in v0.8.0
HashProjectPath generates SHA256 hash of absolute project path This matches Gemini CLI's project hash algorithm for session storage VERIFIED: echo -n "/Users/ashesh" | shasum -a 256 NOTE: Must resolve symlinks (e.g., /tmp -> /private/tmp on macOS)
func HeartbeatDaemonStale ¶ added in v1.9.63
HeartbeatDaemonStale reports whether an installed heartbeat daemon (launchd plist on macOS, systemd service on Linux) references a script path other than the conductor's currently-resolved <ConductorNameDir>/heartbeat.sh. This is the surface that goes stale when [conductor].dir changes: MigrateConductorHeartbeatScripts refreshes the rendered script content, but the daemon still invokes the old absolute path and is only regenerated by 'conductor setup'.
Detection is side-effect-free (it reads the installed unit and makes no launchctl/systemctl calls) and best-effort: a missing or unreadable daemon returns false, since there is nothing installed to warn about.
func HeartbeatPlistLabel ¶ added in v0.13.0
HeartbeatPlistLabel returns the launchd label for a conductor's heartbeat
func HeartbeatPlistPath ¶ added in v0.13.0
HeartbeatPlistPath returns the path where a conductor's heartbeat plist should be installed
func HermesSharedWorkspaceDir ¶ added in v1.9.47
func HermesSharedWorkspaceDir() string
HermesSharedWorkspaceDir returns the base directory Hermes uses for shared workspace sessions enabling multi-agent handoff visibility. If the user config specifies a WorkspaceDir, that is used; otherwise it falls back to a platform-appropriate temp directory.
func HookSessionAnchorPath ¶ added in v0.25.0
HookSessionAnchorPath returns the sidecar file path used to persist the last known non-empty hook session ID for one instance.
func InboxDir ¶ added in v1.7.73
func InboxDir() string
InboxDir returns the directory that holds per-parent inbox files.
func InboxHasPending ¶ added in v1.9.44
InboxHasPending cheaply reports whether the parent has anything to drain — either a non-empty inbox or records recovered in the in-flight WAL. It does NOT consume anything (a single stat per file). Used as the Stop-hook fast path: a leaf / non-parent session (no inbox file) returns false, so the Stop-hook sync flip is inert for it — no block, no stop-block ledger write (audit B12 scope).
func InboxPathFor ¶ added in v1.7.73
InboxPathFor returns the absolute inbox path for a given parent session id. The parent id is treated as a filename and must not contain path separators or shell metacharacters; agent-deck session ids are URL-safe by convention, so this is enforced by sanitizing rather than escaping.
func InboxTTL ¶ added in v1.9.10
InboxTTL returns the configured age past which persisted inbox events are swept. Honors AGENT_DECK_INBOX_TTL (parsed by time.ParseDuration) and falls back to defaultInboxTTL when unset or unparseable.
func InitializeGlobalPool ¶ added in v0.6.2
func InitializeGlobalPool(ctx context.Context, config *UserConfig, sessions []*Instance) (*mcppool.Pool, error)
InitializeGlobalPool creates and starts the global MCP pool
func InjectClaudeHooks ¶ added in v0.16.0
InjectClaudeHooks injects agent-deck hook entries into Claude Code's settings.json. Uses read-preserve-modify-write pattern to preserve all existing settings and user hooks. Returns true if hooks were newly installed, false if already present.
func InjectCursorHooks ¶ added in v1.9.70
InjectCursorHooks injects agent-deck hook entries into ~/.cursor/hooks.json. Uses read-preserve-modify-write to keep existing user hooks. Returns true if hooks were newly installed, false if already present.
func InjectGeminiHooks ¶ added in v0.25.0
InjectGeminiHooks injects agent-deck hook entries into Gemini CLI settings.json. Uses read-preserve-modify-write pattern to preserve all existing settings and user hooks. Returns true if hooks were newly installed, false if already present.
Known limitation: this path does not currently use file locking, so concurrent writers to the same settings.json can race (last-writer-wins).
func InjectHermesHooks ¶ added in v1.9.47
InjectHermesHooks injects agent-deck hook entries into Hermes's config.yaml. Uses read-preserve-modify-write to keep all existing config keys intact. Serialized via per-config in-process mutex + cross-process flock so two concurrent writers (e.g. TUI auto-inject + CLI `agent-deck hermes-hooks`) can't tear each other's merge. Returns true if hooks were newly installed, false if already present.
func InstallBridgeDaemon ¶ added in v0.16.0
InstallBridgeDaemon installs and starts the bridge daemon. macOS: launchd plist; Linux: systemd user service. Returns the unit/plist file path on success.
func InstallBridgeScript ¶ added in v0.12.0
func InstallBridgeScript() error
InstallBridgeScript copies bridge.py to the conductor base directory. It writes from the embedded const.
func InstallHeartbeatDaemon ¶ added in v0.16.0
InstallHeartbeatDaemon installs and starts the heartbeat timer for a conductor. macOS: launchd plist; Linux: systemd timer/service pair.
func InstallHeartbeatScript ¶ added in v0.13.0
InstallHeartbeatScript writes the heartbeat.sh script for a conductor. This is a standalone heartbeat that works without Telegram.
The script embeds the conductor root (renderConductorHeartbeatScript's {CONDUCTOR_ROOT} substitution, which honors [conductor].dir) as a literal. When [conductor].dir changes, the script content does NOT go stale silently: MigrateConductorHeartbeatScripts rewrites managed scripts on the next conductor command (list / status / setup / teardown). The stale surface is the daemon, not the script — GenerateHeartbeatPlist bakes absolute script/log paths into the launchd plist (and systemd unit) at setup time and is regenerated only by 'conductor setup', so re-run 'conductor setup' per conductor after a dir change to reload the daemon.
func InstallLearningsMD ¶ added in v0.19.11
func InstallLearningsMD() error
InstallLearningsMD writes the default LEARNINGS.md to the conductor base directory. This is the shared (Tier 1) learnings file for generic patterns across all conductors.
func InstallPolicyMD ¶ added in v0.19.6
InstallPolicyMD writes the default POLICY.md to the conductor base directory, or creates a symlink if customPath is provided. This contains agent behavior rules (auto-response policy, escalation guidelines).
func InstallSharedClaudeMD ¶ added in v0.12.0
InstallSharedClaudeMD writes the shared CLAUDE.md to the conductor base directory. Deprecated: use InstallSharedConductorInstructions with the Claude agent.
func InstallSharedConductorInstructions ¶ added in v0.26.4
InstallSharedConductorInstructions writes the shared instructions file for the given conductor agent, or creates a symlink if customPath is provided.
func InstallTransitionNotifierDaemon ¶ added in v0.19.13
InstallTransitionNotifierDaemon installs and starts the transition notifier daemon.
func InvalidateProjectMCPIntegrationsCache ¶ added in v1.9.47
func InvalidateProjectMCPIntegrationsCache(projectPath string)
InvalidateProjectMCPIntegrationsCache clears session caches used by Claude-style, Cursor, and OpenCode MCP reads.
func IsAtCap ¶ added in v1.9.1
IsAtCap reports whether the running count has reached the cap for a group. max <= 0 is treated as unlimited (never at cap).
func IsBridgeDaemonRunning ¶ added in v0.16.0
func IsBridgeDaemonRunning() bool
IsBridgeDaemonRunning checks if the bridge daemon is currently running.
func IsClaudeCompatible ¶ added in v0.21.0
IsClaudeCompatible returns true if the tool is "claude" or a custom tool whose underlying command is "claude". Use this for capability gates (session tracking, MCP, skills, hooks, etc.) where custom tools wrapping Claude should get full Claude functionality.
func IsClaudeConfigDirExplicit ¶ added in v0.8.12
func IsClaudeConfigDirExplicit() bool
IsClaudeConfigDirExplicit returns true when any priority level (env, profile, global) sets the dir.
func IsClaudeConfigDirExplicitForGroup ¶ added in v1.5.4
IsClaudeConfigDirExplicitForGroup returns true when any priority level sets the dir.
func IsClaudeConfigDirExplicitForInstance ¶ added in v1.5.4
IsClaudeConfigDirExplicitForInstance returns true if ANY priority level sets a config dir for this Instance.
func IsCodexCompatible ¶ added in v1.7.35
IsCodexCompatible returns true if the tool is "codex" or a custom tool whose underlying command is "codex". Use this for capability gates where custom tools wrapping Codex should get full Codex functionality without losing their configured tool identity.
func IsConductorHeartbeatMessage ¶ added in v1.9.20
func IsConductorSetup ¶ added in v0.12.0
IsConductorSetup checks if a named conductor is set up by verifying meta.json exists
func IsHTTPServerRunning ¶ added in v0.8.96
IsHTTPServerRunning checks if an HTTP MCP server is running
func IsHermesGatewayReachable ¶ added in v1.9.47
IsHermesGatewayReachable performs a basic reachable check against the configured GatewayURL from HermesSettings. Returns true if a simple HTTP request succeeds within timeout. Keeps existing process-alive logic untouched; this augments status detection when gateway URL is available.
func IsRemovableWorktree ¶ added in v1.9.38
IsRemovableWorktree reports whether agent-deck may safely delete the session's worktree directory on dismiss. It is deliberately conservative: a path is removable ONLY when every check passes, because a false positive means os.RemoveAll on a real repository (issue #1200 — silent data loss).
All of the following must hold:
- agent-deck recorded a worktree at all (non-empty WorktreePath and WorktreeRepoRoot). A worktree_reuse session that pointed at the original repo can leave these empty depending on the creation path.
- the worktree path is NOT the original repo root (worktree_reuse points WorktreePath at the user's primary working tree).
- git itself confirms the path is a LINKED worktree (created via `git worktree add`), never the main working tree or a non-worktree dir. This is the location-independent proof that agent-deck created it: worktree placement is user-configurable, so a fixed managed-directory prefix check is unreliable.
func IsTelegramOfficialRefusal ¶ added in v1.9.2
IsTelegramOfficialRefusal reports whether (name, source) pair is the exact "telegram@claude-plugins-official" id refused in v1. The check is case-sensitive — the upstream catalog uses these literal strings.
func IsTransitionNotifierDaemonRunning ¶ added in v0.19.13
func IsTransitionNotifierDaemonRunning() bool
IsTransitionNotifierDaemonRunning checks if transition notifier daemon is running.
func IsValidEnvKey ¶ added in v1.10.9
IsValidEnvKey reports whether key is a valid POSIX-style environment variable name.
func IsValidSessionColor ¶ added in v1.7.70
IsValidSessionColor validates a per-session color tint (issue #391). Accepts "", "#RRGGBB" hex, or ANSI 256-palette decimal "0".."255".
func LaunchdPlistPath ¶ added in v0.12.0
LaunchdPlistPath returns the path where the plist should be installed
func ListProfiles ¶ added in v0.3.0
ListProfiles returns all available profile names
func LoadSkillSources ¶ added in v0.19.1
func LoadSkillSources() (map[string]SkillSourceDef, error)
LoadSkillSources loads the global source registry. If no registry exists yet, defaults are returned.
func LocateConversationConfigDir ¶ added in v1.10.9
func LocateConversationConfigDir(cfg *UserConfig, inst *Instance, extraCandidates ...string) (configDir, sessionID string, size int64)
LocateConversationConfigDir finds the Claude config dir that actually holds the instance's conversation file on disk (#1571).
Why: legacy session records carry an empty Account field. The config-dir resolver then falls through to env/profile/global defaults, which can lexically equal the switch target — MigrateConversationFrom sees src == dst, returns ("", nil), and switch-account declares "nothing to migrate" while the real conversation sits in a different profile's config dir. The restarted `claude --resume <id>` then finds no conversation and the session loops in error. The disk is authoritative: scan every configured config dir for projects/<encoded-cwd>/<sid>.jsonl and treat the dir that contains it as the source.
Candidates scanned (deduped, in order): extraCandidates (callers pass the resolver's answer first), every [profiles.<name>.claude].config_dir, the global [claude].config_dir, and ~/.claude.
When inst.ClaudeSessionID is set, only exact <sid>.jsonl matches count and the LARGEST match wins — a poisoned few-hundred-byte stub left by a failed restart must never shadow the real conversation. When the id is empty, the newest UUID-named conversation file across all candidates wins.
Returns ("", "", 0) when no conversation exists anywhere (fresh session).
func LogCgroupIsolationDecision ¶ added in v1.5.4
func LogCgroupIsolationDecision()
LogCgroupIsolationDecision emits exactly one structured log line per process describing the cgroup isolation decision the runtime made. The emitted message is one of these four exact strings (pinned by TestLogCgroupIsolationDecision_*):
- "tmux cgroup isolation: enabled (systemd-run detected)"
- "tmux cgroup isolation: disabled (systemd-run not available)"
- "tmux cgroup isolation: enabled (config override)"
- "tmux cgroup isolation: disabled (config override)"
Decision logic mirrors GetLaunchInUserScope: an explicit (non-nil) LaunchInUserScope wins, otherwise systemdAvailableForLog() decides. The sync.Once guarantees one-line-per-process even when called from multiple goroutines.
Satisfies OBS-01. Intended to be called once from the application bootstrap (cmd/agent-deck/main.go) immediately after logging.Init so the line lands in ~/.agent-deck/debug.log via lumberjack.
func MCPGlobalConfigPathForTool ¶ added in v1.9.47
MCPGlobalConfigPathForTool returns the global MCP config path for display and writes.
func MCPLocalConfigPathForTool ¶ added in v1.9.47
MCPLocalConfigPathForTool returns the project-local MCP config path for display and writes. Empty when the tool has no project-local MCP file (e.g. Gemini uses global settings only).
func MarshalToolOptions ¶ added in v0.8.39
func MarshalToolOptions(opts ToolOptions) (json.RawMessage, error)
MarshalToolOptions serializes tool options to JSON
func MatchTool ¶ added in v1.9.47
MatchTool resolves a command string to a tool name using the process registry. It is the exported seam that cmd/agent-deck's detectTool() wraps.
NOTE: Match never consults the show_only_installed_tools filter — resolution is display-independent, so the CLI dispatch path keeps spawning tools that the dialogs would hide (issue #1259 non-goal: display filter only, not a gate).
func MergeToolPatterns ¶ added in v0.9.2
func MergeToolPatterns(toolName string) *tmux.RawPatterns
MergeToolPatterns returns merged RawPatterns for a tool, combining built-in defaults with any user overrides/extras from config.toml. Works for ALL tools: built-in (claude, gemini, etc.) and custom. Returns nil only if there are no defaults AND no config entry.
func MigrateClaudeProjectDir ¶ added in v1.7.28
MigrateClaudeProjectDir moves ~/.claude/projects/<oldSlug>/ to ~/.claude/projects/<newSlug>/ so `claude --resume` in the new project location picks up the prior conversation history.
- No-op when the source dir doesn't exist (fresh sessions have nothing).
- If copy=true, the source is preserved and the destination gets a recursive copy — useful when other sessions still reference oldPath.
- If copy=false (default), rename is attempted; falls back to copy+remove across filesystems.
- Errors when the destination already exists to avoid silent overwrite.
func MigrateConductorHeartbeatScripts ¶ added in v0.19.14
MigrateConductorHeartbeatScripts refreshes managed heartbeat scripts to the current template without touching custom user-authored scripts.
func MigrateConductorLearnings ¶ added in v0.19.11
MigrateConductorLearnings backfills LEARNINGS.md files for existing conductors and updates per-conductor CLAUDE.md startup checklists to include the LEARNINGS.md reading step. It only rewrites non-symlink CLAUDE.md files that exactly match the pre-learnings generated template. Returns the names of conductors that were updated.
func MigrateConductorPolicySplit ¶ added in v0.19.6
MigrateConductorPolicySplit updates legacy generated per-conductor CLAUDE.md templates to include POLICY.md instructions. It only rewrites non-symlink CLAUDE.md files that exactly match the legacy generated template.
func MigrateConversation ¶ added in v1.9.55
MigrateConversation copies the session's Claude conversation file from its currently resolved config dir into targetConfigDir, so `claude --resume` finds the history after an account switch (#924 follow-up). Copy-only: the source is never modified or deleted. Returns the destination path, or "" when source and target resolve to the same directory (no-op).
Validated against two real accounts (2026-06-10): `--resume <id>` is a pure file lookup under <config-dir>/projects/<encoded-path>/<id>.jsonl; the id is not bound to the logged-in account.
func MigrateConversationFrom ¶ added in v1.9.55
MigrateConversationFrom is MigrateConversation with an explicit source config dir. Callers that mutate inst.Account before migrating must capture the old account's dir first — after the mutation the resolver returns the new dir.
When the stored ClaudeSessionID has no file on disk (resume renames conversations to a fresh UUID), it falls back to the newest conversation file in the project dir and updates inst.ClaudeSessionID accordingly; the caller is responsible for persisting the instance.
func MigrateLegacyConductors ¶ added in v0.12.0
MigrateLegacyConductors scans for conductor dirs that have CLAUDE.md but no meta.json, and creates meta.json for them. Returns the names of migrated conductors.
func NeedsMigration ¶ added in v0.3.0
NeedsMigration checks if migration from old layout is needed
func OtherSessionsShareWorktree ¶ added in v1.9.65
OtherSessionsShareWorktree reports whether any instance in others (other than target itself) still references target's worktree directory. Paths are compared canonically (EvalSymlinks), so a symlinked alias to the same directory counts as a sharer. nil entries, target's own ID, and non-worktree instances are skipped.
This is the missing guard behind issue #1449: finishing one of several sessions that share a single worktree must not remove the shared directory or delete the branch while a sibling session still uses it.
func ParseIdleTimeoutFlag ¶ added in v1.9.29
ParseIdleTimeoutFlag parses a --idle-timeout flag value: a Go-style duration like "30m", "1h", "24h". Empty string and "0" mean disabled (returns 0). Negative durations are rejected so a typo like "-1s" surfaces loudly instead of silently disabling. Returns the value in seconds (int64).
func PickerToolNames ¶ added in v1.9.54
func PickerToolNames() []string
PickerToolNames returns tool names for the new-session picker after applying hidden_tools and show_only_installed_tools. The empty command "" is mapped to "shell" for web consumers.
func PreAcceptClaudeTrust ¶ added in v1.9.31
PreAcceptClaudeTrust adds a `projects[parentDir].hasTrustDialogAccepted = true` entry to the Claude config at claudeJSONPath, preserving all existing top-level fields and project entries.
Why this exists (#1149): multi-repo worktree parent dirs live at synthetic paths under ~/.agent-deck/multi-repo-worktrees/ with no .claude/ or .git markers, so Claude Code prompts "do you trust this directory?" on every launch. The trust state is keyed by the literal parentDir string in ~/.claude.json — pre-seeding the entry skips the prompt the same way that accepting it in the UI would.
If claudeJSONPath does not exist, it is created with the new entry. If it exists but is malformed, an error is returned without touching the file.
func PreAcceptCodexSandboxWorkspaceTrust ¶ added in v1.10.9
PreAcceptCodexSandboxWorkspaceTrust seeds trust for the sandbox workspace in the host-side sandbox copy of ~/.codex/config.toml. Call after docker.RefreshAgentConfigs so host file sync does not overwrite the entry.
func PreAcceptCodexTrust ¶ added in v1.10.9
PreAcceptCodexTrust adds `projects[projectDir].trust_level = "trusted"` to the Codex config at codexConfigPath, preserving all existing top-level fields and project entries.
Why this exists: Codex prompts "do you trust the files in this folder?" on first launch in a directory, blocking unattended TUI/headless session starts. The trust state is keyed by the literal projectDir string in ~/.codex/config.toml — pre-seeding the entry skips the prompt the same way accepting it in the UI would.
projectDir must be the workspace root Codex will see (absolute on the host for normal sessions, /workspace inside sandbox containers).
func PreAcceptCursorTrust ¶ added in v1.10.9
PreAcceptCursorTrust seeds ~/.cursor/projects/<key>/.workspace-trusted for workspacePath so interactive `cursor agent` skips the workspace-trust prompt.
Cursor keys trust by the literal workspace path (stored in the JSON file) and a slugified directory under ~/.cursor/projects/. Pre-seeding matches what accepting the prompt in the UI would write. This mirrors Cursor's observed trust format as of 2026-06; if Cursor changes that schema, seeding may become a harmless no-op until this mapping is updated.
If the trust file already exists it is left unchanged. Malformed existing trust files are not overwritten.
func PreAcceptCursorTrustInContainer ¶ added in v1.10.9
PreAcceptCursorTrustInContainer seeds Cursor workspace trust inside a sandbox container via docker exec.
func PreAcceptCursorTrustSSH ¶ added in v1.10.9
PreAcceptCursorTrustSSH seeds Cursor workspace trust on a POSIX remote host via ssh. Project-key generation assumes matching POSIX path semantics locally.
func ProfileExists ¶ added in v0.3.0
ProfileExists checks if a profile exists
func PruneCodexMCPCache ¶ added in v1.10.9
PruneCodexMCPCache removes stale Codex MCP cache entries.
func PruneCursorMCPCache ¶ added in v1.9.47
PruneCursorMCPCache removes stale Cursor MCP cache entries (TTL), like PruneMCPCache.
func PruneMCPCache ¶ added in v0.10.6
PruneMCPCache removes cache entries older than maxAge to prevent unbounded growth. Called periodically from the TUI tick handler.
func PruneOpenCodeMCPCache ¶ added in v1.9.59
PruneOpenCodeMCPCache removes stale OpenCode MCP cache entries (TTL).
func ReadFocusRequest ¶ added in v1.10.9
ReadFocusRequest returns the raw stored payload ("" if none).
func ReadHookSessionAnchor ¶ added in v0.25.0
ReadHookSessionAnchor reads the persisted hook session ID sidecar.
func ReadIdleTimeoutSecsFromToolData ¶ added in v1.9.29
func ReadIdleTimeoutSecsFromToolData(td json.RawMessage) int64
ReadIdleTimeoutSecsFromToolData extracts idle_timeout_secs from the blob. Returns 0 (disabled) for missing/malformed/legacy rows.
func ReadPrevEventStatus ¶ added in v0.18.0
ReadPrevEventStatus reads the previous status from an existing event file. Returns empty string if no event file exists or can't be read.
func ReconcileDeclarativeGroups ¶ added in v1.10.9
func ReconcileDeclarativeGroups(tree *GroupTree, cfg *UserConfig) bool
ReconcileDeclarativeGroups creates groups declared with create = true under [groups."<path>"] in cfg, and applies default_path to declared or existing groups. It never deletes a group and never clears a field config omits, and returns true if the tree was modified.
Callers must pass a tree loaded with the full stored group set.
func RefreshInstancesForCLIStatus ¶ added in v1.7.9
func RefreshInstancesForCLIStatus(instances []*Instance)
RefreshInstancesForCLIStatus is the CLI analogue of SessionDataService.refreshStatuses (internal/web) and Home.backgroundStatusUpdate (internal/ui). CLI JSON emitters must call it before iterating inst.UpdateStatus() so the tmux pane-title cache is warm and on-disk hook statuses are loaded — without this step the title fast-path in tmux.GetStatus cannot fire and long-running Claude sessions get reported as idle/waiting instead of running (issue #610).
Symmetry with the other two surfaces:
- internal/ui/home.go:backgroundStatusUpdate
- internal/web/session_data_service.go:refreshStatuses
func RefreshStalePluginPins ¶ added in v1.9.9
RefreshStalePluginPins scans mcpFile for mcpServers entries whose `command` or `args` strings reference <profileDir>/plugins/cache/<source>/<name>/<version>/... where <version> no longer exists on disk. Each stale reference is rewritten to point at the most recently installed version directory under the same <source>/<name>. Returns the number of entries that were modified.
If mcpFile does not exist, returns (0, nil). If no profileDirs are supplied or no pins are stale, returns (0, nil) without touching the file.
func RemoveClaudeHooks ¶ added in v0.16.0
RemoveClaudeHooks removes agent-deck hook entries from Claude Code's settings.json. Returns true if hooks were removed, false if none found.
func RemoveCursorHooks ¶ added in v1.9.70
RemoveCursorHooks removes agent-deck hook entries from ~/.cursor/hooks.json. Returns true if hooks were removed, false if none found.
func RemoveGeminiHooks ¶ added in v0.25.0
RemoveGeminiHooks removes agent-deck hook entries from Gemini CLI settings.json. Returns true if hooks were removed, false if none found.
func RemoveHeartbeatPlist ¶ added in v0.13.0
RemoveHeartbeatPlist removes the launchd plist for a conductor's heartbeat
func RemoveHermesHooks ¶ added in v1.9.47
RemoveHermesHooks removes agent-deck hook entries from Hermes's config.yaml. Serialized via per-config in-process mutex + cross-process flock (see InjectHermesHooks). Returns true if hooks were removed, false if none found.
func RemoveNotifyStateRecord ¶ added in v1.9.1
RemoveNotifyStateRecord drops records[childSessionID] from runtime/transition-notify-state.json. Returns whether a record was actually present.
Idempotent: a second call on the same id returns (false, nil). A missing state file is treated as "no record present".
Issue #910 — the dedup ledger must release the removed child's slot, otherwise the next genuinely-delivered transition for a reused id would be (incorrectly) dedup-suppressed against the stale row.
func RemoveSessionWorktree ¶ added in v1.9.38
RemoveSessionWorktree removes the session's worktree directory if and only if IsRemovableWorktree permits it. It returns whether a removal was performed. A reused original repo (or any non-linked-worktree path) is left untouched — the caller should simply drop the session from the registry (#1200).
func RemoveSessionWorktreeUnlessShared ¶ added in v1.9.65
func RemoveSessionWorktreeUnlessShared(inst *Instance, others []*Instance) (removed bool, err error)
RemoveSessionWorktreeUnlessShared is the shared-worktree-safe entry point (issue #1449). It removes the session's worktree directory only when no OTHER live session still references that worktree; otherwise it skips the destructive git steps and reports removed=false so the caller merely detaches this session from the registry. When this is the last sharer, behaviour is identical to RemoveSessionWorktree (including the #1200 reuse guard).
func RemoveSkillSource ¶ added in v0.19.1
RemoveSkillSource removes a named source.
func ResetInboxFingerprintCacheForTest ¶ added in v1.7.74
func ResetInboxFingerprintCacheForTest()
ResetInboxFingerprintCacheForTest clears the process-local dedup cache. Tests use it to simulate a fresh process so the on-disk recovery path is exercised. Production code does not call this.
func ResetStopBlockBudget ¶ added in v1.9.44
func ResetStopBlockBudget(instanceID string)
ResetStopBlockBudget clears an instance's consecutive-block counter. Used by rm_sweep on removal and available to tests.
func ResolveCostLineTemplate ¶ added in v1.7.75
func ResolveCostLineTemplate(cfg *UserConfig, profile string) (template string, hideWhenZero bool)
ResolveCostLineTemplate returns the active status-bar cost-line template and hide-when-zero flag, applying the resolution chain:
profile.costs > [costs] > hardcoded "{cost_today} today" (template, default true for hide)
Pointer semantics:
- nil at any level falls through to the next level
- explicit empty string for template disables the segment (returned as "")
- explicit bool for hide_when_zero is honored at that level
Safe to call with cfg == nil; returns the hardcoded default + true.
func ResolveTheme ¶ added in v0.15.0
func ResolveTheme() string
ResolveTheme resolves the configured theme to "dark" or "light". If theme is "system", detects the OS dark mode setting. Falls back to "dark" on detection failure.
func RestoreFromArchive ¶ added in v0.8.79
RestoreFromArchive moves all files from archive/ subdirectories back to their parent directories under baseDir/profiles/*/.
func RestoreOrphanedConversationBackup ¶ added in v1.10.9
RestoreOrphanedConversationBackup restores a conversation whose live <id>.jsonl went missing but whose most recent <id>.jsonl.bak-<epoch> orphan still exists in the project dir (the #1533 data-loss residue). It is a no-op when a live <id>.jsonl is already present, when inst has no ClaudeSessionID, or when no matching .bak- orphan exists. Returns the restored path (or "" for no-op) and any error.
func SaveConductorMeta ¶ added in v0.12.0
func SaveConductorMeta(meta *ConductorMeta) error
SaveConductorMeta writes meta.json for a conductor. It takes the conductor base lock so a write cannot interleave with an in-flight `migrate-dir` (enumerate→copy→verify→commit→remove) and be stranded at the old base or deleted with the source tree (finding #5). Internal callers that already hold the lock must use saveConductorMetaLocked instead.
func SaveConfig ¶ added in v0.3.0
SaveConfig saves the global configuration
func SaveProjectSkillsManifest ¶ added in v0.19.1
func SaveProjectSkillsManifest(projectPath string, manifest *ProjectSkillsManifest) error
SaveProjectSkillsManifest writes project attachment state atomically.
func SaveSkillSources ¶ added in v0.19.1
func SaveSkillSources(sources map[string]SkillSourceDef) error
SaveSkillSources writes the source registry atomically.
func SaveUserConfig ¶ added in v0.8.15
func SaveUserConfig(config *UserConfig) error
SaveUserConfig writes the config to config.toml using atomic write pattern. This clears the cache so next LoadUserConfig() reads fresh values.
Guarded path: it backs up the existing config.toml to config.toml.bak before overwriting (S2) and REFUSES a save that would drop a populated [mcps] or [groups] section to empty (S3, ErrRefusingConfigSectionDrop). Both are data-loss safeguards from the 2026-06-04 incident. Callers that genuinely intend to clear all MCPs/groups must use SaveUserConfigWithIntent.
func SaveUserConfigWithIntent ¶ added in v1.9.48
func SaveUserConfigWithIntent(config *UserConfig, allowSectionDrop bool) error
SaveUserConfigWithIntent is SaveUserConfig with an explicit opt-out of the S3 section-drop guard. Pass allowSectionDrop=true only when the user genuinely wants to clear all [mcps] or [groups] entries; the default false path refuses such a save (ErrRefusingConfigSectionDrop) because zeroing a populated section is almost always a partially-built-config bug, not deliberate intent.
The S2 config.toml.bak backup is taken on BOTH paths: the atomic rename prevents torn writes but not semantic clobbering, so the .bak is the recovery net regardless of intent.
func SaveWatcherMeta ¶ added in v1.5.1
func SaveWatcherMeta(meta *WatcherMeta) error
SaveWatcherMeta writes meta.json for a watcher. Creates the watcher directory if it does not exist.
Uses a write-temp-rename atomic pattern: data is first written to meta.json.tmp, then renamed into place via os.Rename (POSIX atomic on the same filesystem). A mid-write crash therefore leaves either the old meta.json intact or the new meta.json complete — never a partial write. Any stale .tmp from a previous crash is overwritten on the next Save and removed on rename failure.
func ScrubProcessEnvForChildLaunch ¶ added in v1.9.9
func ScrubProcessEnvForChildLaunch(inst *Instance)
ScrubProcessEnvForChildLaunch removes TELEGRAM_* vars from the CURRENT process environment when the given instance represents a non-channel-owning claude child. Issue #955: `agent-deck launch` invoked from a conductor session inherits the conductor's TELEGRAM_STATE_DIR; without this strip the var propagates into the tmux server (which inherits the launching process env on first `new-session`) and from there into every subprocess in the new pane — Bash-tool spawns, fork claudes, restart respawn — even when the S8 exec-layer protects the immediate claude binary. Any of those descendants can load the Claude Code telegram plugin and start a second `bun telegram` poller against the conductor's bot, racing the conductor for the Bot API lock (HTTP 409) and silently dropping inbound messages.
#1133 broadens the scrub: TELEGRAM_STATE_DIR alone left a hole — conductors that also exported TELEGRAM_BOT_TOKEN (the plugin re-derives state from the token) still leaked the duplicate poller. We now unset every var named in telegramEnvVarsToStrip *and* any other TELEGRAM_-prefixed var present in os.Environ. The prefix sweep means a future plugin env addition (TELEGRAM_API_HASH etc.) is covered without code change here.
Reuses telegramStateDirStripExpr as the single source of truth for the strip predicate so this layer can never disagree with the shell-level and exec-level layers about which sessions own the telegram bot. No-op for conductors, explicit telegram channel owners, --inherit-telegram-env opt-ins, and non-claude tools.
func SelfHealAuditPath ¶ added in v1.9.67
SelfHealAuditPath returns the durable NDJSON audit path for a profile. It uses the configured AuditPath override when set, else a per-profile default under the agent-deck data dir (so the ≥1-week observe window's records survive restarts and are easy to locate for review). profile may be "" (default).
func SendSessionMessageReliable ¶ added in v0.19.13
SendSessionMessageReliable sends a message using the same queued/reliable semantics as `agent-deck session send` (default mode, no --no-wait). It invokes the CLI command to keep behavior identical across callers.
func SetDefaultProfile ¶ added in v0.3.0
SetDefaultProfile sets the default profile in the config
func SetField ¶ added in v1.7.70
func SetField(inst *Instance, field, value string, extraArgsTokens []string) (oldValue string, postCommit func(), err error)
SetField is the single source of truth for session metadata edits — both `agent-deck session set` and the TUI EditSessionDialog call it.
postCommit is non-nil for fields that need a slow tmux subprocess (claude/gemini session-id env propagation). TUI callers must drop instancesMu before invoking it so the subprocess doesn't stall background readers; CLI callers run it inline.
extraArgsTokens supplies pre-tokenized argv for FieldExtraArgs (CLI path); when nil, FieldExtraArgs falls back to strings.Fields(value) (TUI path).
Persistence is the caller's responsibility.
func SetFsyncHookForTest ¶ added in v1.9.47
SetFsyncHookForTest swaps the durable-write fsync seam and returns a restore func (defer it). The two perf gates added for the durable per-parent outbox use it: the Tier 2 test wraps it with a counter to assert N messages drain in a CONSTANT number of fsyncs, and the Tier 1 WARM walltime test no-ops it to isolate in-process Go cost from disk. Production never calls it. Package tests run serially (no t.Parallel on the perf tests), so the global swap is safe.
func SetGroupSortMode ¶ added in v1.9.73
func SetGroupSortMode(mode string)
SetGroupSortMode updates the cached within-group sort mode. Any value other than "actionable" normalizes to "creation".
func SetSSHRunnerRunFnForTest ¶ added in v1.9.24
SetSSHRunnerRunFnForTest assigns the unexported runFn field on an SSHRunner. Tests in other packages (notably internal/ui) need this to stub out SSH command execution without spawning a real ssh subprocess.
Do not call from non-test code; runFn is meant for test injection only.
func SetupConductor ¶ added in v0.12.0
func SetupConductor(name, profile string, heartbeatEnabled bool, clearOnCompact bool, description string, customClaudeMD string, customPolicyMD string, customHeartbeatRulesMD string, env map[string]string, envFile string) error
SetupConductor creates a Claude conductor for backward compatibility. New callers should prefer SetupConductorWithAgent.
func SetupConductorProfile ¶ added in v0.12.0
SetupConductorProfile creates a default Claude conductor for a profile. Deprecated: Use SetupConductor instead. Kept for backward compatibility.
func SetupConductorWithAgent ¶ added in v0.26.4
func SetupConductorWithAgent(name, profile, agent string, heartbeatEnabled bool, clearOnCompact bool, description string, customInstructionsMD string, customPolicyMD string, customHeartbeatRulesMD string, env map[string]string, envFile string, heartbeatIdleMinutes ...int) error
SetupConductorWithAgent creates the conductor directory, agent-specific instructions file, and meta.json. If customInstructionsMD is provided, creates a symlink instead of writing the template. If customPolicyMD is provided, creates a per-conductor POLICY.md symlink (overrides the shared POLICY.md). If customHeartbeatRulesMD is provided, creates a per-conductor HEARTBEAT_RULES.md symlink (overrides the shared HEARTBEAT_RULES.md and any per-profile override). It does NOT register the session (that's done by the CLI handler which has access to storage).
func ShouldNotifyTransition ¶ added in v0.19.13
func ShouldQueue ¶ added in v1.9.1
ShouldQueue reports whether a new launch into groupPath must be queued because the group is at its MaxConcurrent cap.
func ShouldRecycleForVersion ¶ added in v1.9.43
ShouldRecycleForVersion reports whether a long-lived daemon should exit so the supervisor restarts it on a freshly-upgraded binary. STEP 1 of issue #1214: the transition-notifier unit is Restart=always, so a clean exit on a version change guarantees it never keeps running 20-day-stale code. It only recycles on a definite mismatch — empty/unknown versions never trigger a flap.
func ShouldRestartProjectSkills ¶ added in v1.7.32
ShouldRestartProjectSkills reports whether agent-deck should auto-restart the session after project skill changes for this runtime.
func ShouldSkipRestart ¶ added in v1.7.39
ShouldSkipRestart decides whether `agent-deck session restart` should short-circuit instead of calling Instance.Restart().
Returns skip=true (with a human-readable reason) when the session is in a healthy state AND was started within restartFreshnessWindow. The force parameter (wired to the CLI --force flag) bypasses the guard.
A zero LastStartedAt is treated as "unknown" — we always permit the restart so users can recover legacy records.
func ShutdownGlobalPool ¶ added in v0.6.2
ShutdownGlobalPool stops the global pools if shouldShutdown is true. If shouldShutdown is false, it disconnects from the pools but leaves processes running.
func SlugifyClaudeProjectPath ¶ added in v1.7.28
SlugifyClaudeProjectPath converts a project path to Claude Code's encoded directory name format. Claude stores conversation history in ~/.claude/projects/<slug>/ where <slug> is the project path with `/` and `.` replaced by `-`.
Example: /home/user/foo.v1 → -home-user-foo-v1
Kept in the session package so both the costs sync path and the CLI `session move` command share one implementation (issue #414).
func SortInstancesByActionable ¶ added in v1.9.7
func SortInstancesByActionable(insts []*Instance)
SortInstancesByActionable sorts the given slice in place according to the active within-group sort mode (see SetGroupSortMode), while honoring per-session pins (pin-sessions feature). The outermost key is the pin zone (see pinZone); within the normal zone the sort depends on mode:
- "creation" (default): Order asc only — sessions keep their creation / K/J manual order unchanged.
- "actionable" (issue #857): status→recency tiers apply before Order so the most recently actionable sessions surface first.
Pin-top and pin-bottom bands are always ordered by Order alone (fully fixed — status and recency are ignored, so K/J reordering still works inside a band).
Actionable-mode normal-zone key precedence:
- actionablePriority(Status) asc — error/waiting/running first
- LastAccessedAt desc — recent attention first
- Order asc — preserves user-customized position as a stable tie-breaker (TestSessionOrderPersistence, TestSessionOrderMigration)
func StartHTTPServer ¶ added in v0.8.96
StartHTTPServer starts an HTTP MCP server on demand This is called when an HTTP MCP with server config is attached to a session
func StartMaintenanceWorker ¶ added in v0.8.79
func StartMaintenanceWorker(ctx context.Context, onComplete func(MaintenanceResult))
StartMaintenanceWorker launches a background goroutine that runs maintenance on a 15-minute ticker with an immediate first run. It checks GetMaintenanceSettings().Enabled before each run.
func StreamTranscript ¶ added in v1.7.48
func StreamTranscript(ctx context.Context, path, sessionID string, sentAt time.Time, w io.Writer, cfg StreamConfig) error
StreamTranscript tails path (a Claude session JSONL file) and writes structured StreamEvents as JSONL to w. sentAt gates out records whose timestamp is strictly before (sentAt - 250ms skew) so we don't replay history.
Returns nil on natural end_turn stop, ctx.Err on cancel, or ErrStreamTimeout on idle timeout (with an error event already written).
func StripResumeFields ¶ added in v1.3.2
func StripResumeFields(raw json.RawMessage) json.RawMessage
StripResumeFields removes session-specific fields (resume_session_id, session_mode) from serialized ToolOptionsJSON so that a new session inheriting another session's settings starts fresh instead of resuming the source conversation. Other options (skip_permissions, etc.) are preserved. Returns the input unchanged when it is nil/empty or when unmarshalling fails.
func SupportsLaunchModel ¶ added in v1.9.14
SupportsLaunchModel reports whether a newly-created session can receive an explicit model override through Agent Deck's generic session creation path.
func SupportsProjectSkills ¶ added in v1.7.32
SupportsProjectSkills reports whether the runtime supports project skill materialization.
func SweepInboxByTTL ¶ added in v1.9.10
SweepInboxByTTL walks every inbox file and drops entries older than maxAge (computed against TransitionNotificationEvent.Timestamp). Returns the total entries dropped across all inbox files.
Issue #962 variant: defense-in-depth alongside SweepInboxByTuple. The tuple sweep relies on a future successful transition for the same (child, from, to) to clear stale entries. Children that complete and never transition again would otherwise leave their last persisted entry in the inbox forever. The TTL puts a hard ceiling on inbox growth.
func SweepInboxByTuple ¶ added in v1.9.10
SweepInboxByTuple drops every entry in the parent's inbox file whose (child_session_id, from_status, to_status) matches the given tuple. Returns the count of dropped entries.
Issue #962 variant: when a transition for (child, from, to) is later delivered successfully, any earlier persisted entry for the same tuple represents an event the operator no longer needs to see — the state it described has already been signaled to the conductor by the live send. Without this sweep, the inbox JSONL grows by one entry every time the target is busy at first-attempt time.
Idempotent and best-effort: missing files are not an error. The rewrite is atomic via temp file + rename, mirroring SweepInboxesForChildSession.
func SweepInboxesForChildSession ¶ added in v1.9.1
SweepInboxesForChildSession rewrites every inbox JSONL file under the agent-deck inboxes directory, dropping lines whose child_session_id equals the given id. Returns the total number of lines dropped across all inbox files.
Each rewrite is atomic (temp file + rename). A no-op for absent files, empty inboxes, or inboxes that contain no matching lines.
Issue #910 — without this sweep, the conductor replays persisted inbox events for children that have been removed.
func SyncPluginChannels ¶ added in v1.9.2
func SyncPluginChannels(inst *Instance)
SyncPluginChannels is the cmd/-side entry point (mirrors syncPluginChannels).
func SystemdBridgeServicePath ¶ added in v0.16.0
SystemdBridgeServicePath returns the full path to the bridge systemd service file
func SystemdHeartbeatServiceName ¶ added in v0.16.0
SystemdHeartbeatServiceName returns the systemd service name for a conductor heartbeat
func SystemdHeartbeatServicePath ¶ added in v0.16.0
SystemdHeartbeatServicePath returns the full path to a heartbeat systemd service
func SystemdHeartbeatTimerName ¶ added in v0.16.0
SystemdHeartbeatTimerName returns the systemd timer name for a conductor heartbeat
func SystemdHeartbeatTimerPath ¶ added in v0.16.0
SystemdHeartbeatTimerPath returns the full path to a heartbeat systemd timer
func SystemdTransitionNotifierServicePath ¶ added in v0.19.13
SystemdTransitionNotifierServicePath returns the full path to the transition notifier service file.
func SystemdUserDir ¶ added in v0.16.0
SystemdUserDir returns the systemd user unit directory (~/.config/systemd/user/)
func TakeFocusRequest ¶ added in v1.10.9
TakeFocusRequest atomically reads and clears the pending request in one statement (consume-once). Prefer it over ReadFocusRequest+ClearFocusRequest: the separate read+clear has a window where a concurrent CLI `session focus` write lands between them and is wiped by the clear. Returns "" when none.
func TeardownConductor ¶ added in v0.12.0
TeardownConductor removes the conductor directory for a named conductor. It does NOT remove the session from storage (that's done by the CLI handler).
func TeardownConductorProfile ¶ added in v0.12.0
TeardownConductorProfile removes the conductor directory for a profile. Deprecated: Use TeardownConductor instead. Kept for backward compatibility.
func ThemeColorFGBG ¶ added in v0.26.0
func ThemeColorFGBG() string
ThemeColorFGBG returns the COLORFGBG value for the current resolved theme. Used by tmux session setup to persist the value via set-environment.
func TierName ¶ added in v0.5.3
func TierName(tier SearchTier) string
TierName returns a human-readable name for the tier
func ToggleLocalMCP ¶ added in v0.5.3
ToggleLocalMCP toggles a Local MCP on/off It respects the existing mode (whitelist vs blacklist) or initializes with blacklist
func ToolFilterActive ¶ added in v1.9.48
func ToolFilterActive() bool
ToolFilterActive reports whether show_only_installed_tools is enabled.
func ToolFilterFallbackActive ¶ added in v1.9.48
func ToolFilterFallbackActive() bool
ToolFilterFallbackActive reports whether the empty-fallback engaged (filter on, nothing but shell resolved) so dialogs can surface the "showing all" hint.
func ToolSupportsMCPManager ¶ added in v1.9.47
ToolSupportsMCPManager reports whether the TUI/CLI MCP surfaces apply to this tool.
func TranscriptTailLines ¶ added in v1.9.58
TranscriptTailLines returns up to n trailing non-empty lines of the file, oldest first. It reads at most the trailing 512KB — transcript records are single JSONL lines comfortably under that; a line truncated by the byte cut is discarded rather than half-parsed.
func TransitionNotifierDaemonHint ¶ added in v0.19.13
func TransitionNotifierDaemonHint() string
TransitionNotifierDaemonHint returns how to start transition notifier daemon.
func TransitionNotifierLaunchdPlistPath ¶ added in v0.19.13
TransitionNotifierLaunchdPlistPath returns the launchd plist path for transition notifier.
func TriageDir ¶ added in v1.9.49
TriageDir returns the directory used for watcher triage session results.
func TurnFingerprint ¶ added in v1.9.44
func TurnFingerprint(e TransitionNotificationEvent) string
TurnFingerprint returns a stable identifier for a child's completed TURN, for exactly-once consumer effects (issue #1225). Unlike EventFingerprint — which keys on Timestamp.UnixNano() so a single logical event's retries collapse — TurnFingerprint deliberately omits the emit instant: two emits of the same turn (e.g. the same record re-delivered after a daemon restart that re-stamped Timestamp) share a fingerprint, so the draining parent acts once.
Turn signal precedence:
- finished (one-shot) events: the completion outcome (status + summary)
- interactive transitions: the child's pane-content hash at the flip (LastOutputHash), which advances once per turn
- fallback: the from→to flip
Format "<child_id>@<hex16>" keeps it greppable and child-scoped.
func UninstallBridgeDaemon ¶ added in v0.16.0
func UninstallBridgeDaemon() error
UninstallBridgeDaemon stops and removes the bridge daemon.
func UninstallHeartbeatDaemon ¶ added in v0.16.0
UninstallHeartbeatDaemon stops and removes the heartbeat timer for a conductor.
func UninstallTransitionNotifierDaemon ¶ added in v0.19.13
func UninstallTransitionNotifierDaemon() error
UninstallTransitionNotifierDaemon stops and removes the transition notifier daemon.
func UpdateClaudeSessionsWithDedup ¶ added in v0.4.1
func UpdateClaudeSessionsWithDedup(instances []*Instance)
UpdateClaudeSessionsWithDedup clears duplicate Claude session IDs across instances. The oldest session (by CreatedAt) keeps its ID, newer duplicates are cleared. With tmux env being authoritative, duplicates shouldn't occur in normal use, but we handle them defensively for loaded/migrated sessions.
func UpdateGeminiAnalyticsFromDisk ¶ added in v0.8.35
func UpdateGeminiAnalyticsFromDisk(projectPath, sessionID string, analytics *GeminiSessionAnalytics) error
UpdateGeminiAnalyticsFromDisk updates the analytics struct from the session file on disk. Uses mtime caching to skip re-parsing unchanged files (important for 40MB+ session files).
func UsesClaudeDeliveryVerify ¶ added in v1.9.46
UsesClaudeDeliveryVerify reports whether the Claude-tuned post-send delivery verification (issue #876) should be applied for this tool. That verify keys off Claude-specific TUI signals — an "active" status transition, the composer glyph, and unsent-paste markers. Only Claude-compatible tools surface those; every other tool (codex #1205, codewhale/deepseek #1238, gemini #876, opencode, and custom CLIs) would false-negative the verify and be reported as a silent drop despite successful delivery. Those tools therefore skip the Claude-tuned verify. This is the general superset of #1228's codex-only skip.
func ValidateClaudeExtraArgToken ¶ added in v1.9.59
ValidateClaudeExtraArgToken rejects a single --extra-arg token that looks like a flag mashed together with its value (issue #1431b). Each --extra-arg is shell-quoted as ONE argument, so `--extra-arg "--model opus"` reaches claude as the literal single arg '--model opus' (embedded space) — an unknown flag that makes claude exit on startup and leaves a dead pane the registry still reports as running. A token that starts with '-' AND contains whitespace is almost always two tokens the user meant to pass separately; surfacing it as an error at spawn time beats the silent tmux death. Clean flags ("--model") and clean values ("opus", "be concise") pass.
func ValidateConductorName ¶ added in v0.12.0
ValidateConductorName checks that a conductor name is valid
func ValidateSSHHost ¶ added in v1.9.41
ValidateSSHHost rejects host strings ssh would misinterpret as options rather than a destination. A host beginning with "-" (e.g. "-oProxyCommand=…") is argument injection: passed as a discrete argv element, ssh parses it as a flag and can be coerced into running an arbitrary local command. Whitespace and empty hosts are rejected too. Hosts come from the user's own config, but this closes the option-injection vector cheaply (#1206).
func ValidateTranscriptPath ¶ added in v1.9.58
ValidateTranscriptPath cleans a Claude Code transcript path and applies the same traversal / containment guards as the hook cost path: no "..", and the path must live under ~/.claude (where Claude Code keeps transcripts). Both the hook handler (payload-supplied path) and the daemon (path re-read from a hook status file) gate on this before opening the file.
Containment is fail-closed and boundary-aware. A raw HasPrefix against ~/.claude wrongly accepts sibling directories (e.g. ~/.claude-spoof/x.jsonl, whose string prefix matches but which lives outside the transcript root), so the path must equal the root exactly OR begin with root + path separator. If the home directory cannot be resolved we cannot establish the containment root, so we REJECT rather than fall through (a missing root must never disable containment for a payload-supplied path).
func VerifyConversationInDir ¶ added in v1.10.9
VerifyConversationInDir confirms cfgDir holds the instance's conversation file (#1571 post-switch verification: never report a successful switch when the target dir cannot actually resume the conversation). wantSize > 0 additionally requires the file size to be within 1% of wantSize, so a poisoned stub at the resume path fails loudly instead of masquerading as the migrated conversation.
func VisibleToolNames ¶ added in v1.9.48
func VisibleToolNames() []string
VisibleToolNames returns the names of every tool (built-in + custom) that passes the show_only_installed_tools filter, in built-in-precedence order then sorted custom names. With the flag off it returns the full set. Used by the web new-session dialog to intersect its static tool list.
func WatcherDir ¶ added in v1.5.1
WatcherDir returns the effective base directory for all watchers.
func WatcherNameDir ¶ added in v1.5.1
WatcherNameDir returns the effective directory for a named watcher.
func WorkerScratchDirRoot ¶ added in v1.9.48
WorkerScratchDirRoot returns the worker-scratch root resolved through the XDG data path (~/.local/share/agent-deck/worker-scratch, or the legacy ~/.agent-deck/worker-scratch fallback). It is the exported entry point used by the S5 path-safety guard test so the guard can confirm this sink does not resolve under the real home when un-sandboxed.
func WriteCodexMCPConfig ¶ added in v1.10.9
WriteCodexMCPConfig writes catalog MCPs to $CODEX_HOME/config.toml. Existing non-catalog [mcp_servers.*] tables and non-MCP Codex config text are preserved.
func WriteCompletionRecord ¶ added in v1.9.43
func WriteCompletionRecord(rec CompletionRecord) error
WriteCompletionRecord persists a record atomically (tmp + rename).
func WriteConductorClaudeSettings ¶ added in v1.9.64
WriteConductorClaudeSettings writes (or merges into) the conductor's .claude/settings.json with the auto-allow/ask/deny permission policy from #1358. Claude-only; other agents don't use this file.
Existing unmanaged keys in settings.json are preserved. Managed permission entries are merged in idempotently: re-running setup keeps the policy current without duplicating entries or discarding user-added permissions.
func WriteCursorGlobalMCP ¶ added in v1.9.47
WriteCursorGlobalMCP writes catalog MCPs to ~/.cursor/mcp.json. Preserves other JSON keys if present.
func WriteCursorProjectMCP ¶ added in v1.9.47
WriteCursorProjectMCP writes catalog MCPs to <project>/.cursor/mcp.json (Cursor Agent CLI).
func WriteFocusRequest ¶ added in v1.10.9
WriteFocusRequest stores a select-only focus request for the running TUI to consume.
func WriteFocusRequestAttach ¶ added in v1.10.9
WriteFocusRequestAttach stores a focus request, optionally flagged to attach the session on consume.
func WriteGeminiMCPSettings ¶ added in v0.8.1
WriteGeminiMCPSettings writes MCPs to ~/.gemini/settings.json Preserves existing config fields (security, theme, etc.) Uses a symlink-preserving atomic write (see internal/atomicfile)
func WriteGlobalMCP ¶ added in v0.5.3
WriteGlobalMCP adds or removes MCPs from Claude's global config This modifies ~/.claude-work/.claude.json → mcpServers
func WriteGlobalMCPConfigForTool ¶ added in v1.9.47
WriteGlobalMCPConfigForTool writes enabled catalog MCPs to the tool's global MCP store.
func WriteHookSessionAnchor ¶ added in v0.25.0
func WriteHookSessionAnchor(instanceID, sessionID string)
WriteHookSessionAnchor persists the latest non-empty hook session ID.
func WriteIdleTimeoutSecsToToolData ¶ added in v1.9.29
func WriteIdleTimeoutSecsToToolData(td json.RawMessage, secs int64) json.RawMessage
WriteIdleTimeoutSecsToToolData merges idle_timeout_secs into the given tool_data JSON blob. Passing secs == 0 removes the key (keeps the blob shape identical to a pre-#1143 row, so downgrades stay clean).
func WriteInboxEvent ¶ added in v1.7.73
func WriteInboxEvent(parentSessionID string, event TransitionNotificationEvent) error
WriteInboxEvent appends one event to the parent's inbox as a JSONL line. Safe for concurrent callers within a single process.
Fingerprint dedup: events that share an EventFingerprint with one already persisted in the file are silently skipped. This is the producer-side guard for issue #824 (the same logical event persisted multiple times). The #1225 drain path layers turn_fingerprint dedup on top for at-least-once delivery with exactly-once effects (see inbox_consumer.go).
func WriteLedgerEntry ¶ added in v1.10.9
func WriteLedgerEntry(e CompletionLedgerEntry) error
WriteLedgerEntry persists an entry atomically (tmp + rename), last-wins.
func WriteLocalMCPConfigForTool ¶ added in v1.9.47
WriteLocalMCPConfigForTool writes enabled catalog MCPs to the tool's project-local MCP file.
func WriteMCPJsonFromConfig ¶ added in v0.5.3
WriteMCPJsonFromConfig writes enabled MCPs from config.toml to project's .mcp.json It preserves any existing entries not managed by agent-deck (not defined in config.toml)
func WriteMergedMcpJSONFile ¶ added in v1.9.47
func WriteMergedMcpJSONFile(mcpFile string, enabledNames []string, pluginPinClaudeProfile string) error
WriteMergedMcpJSONFile writes enabled MCPs from config.toml to mcpFile using the Claude/Cursor JSON shape {"mcpServers":{...}}. It preserves entries not defined in config.toml. When pluginPinClaudeProfile is non-empty (Claude project .mcp.json), refreshes stale plugin version pins before merging (#960).
func WriteMultiRepoParentClaudeMD ¶ added in v1.9.31
WriteMultiRepoParentClaudeMD writes a .claude/CLAUDE.md to parentDir telling Claude that this is a multi-repo session, which subdirectories contain real repos, how to scope commands, and @path imports for each child project's CLAUDE.md (front-loading full project context at session start).
Why (#1149): without this hint Claude treats parentDir as a single project and runs build/test commands at the parent — where there is no makefile, no package.json, no .git — so every project-specific command fails until the user manually cds in. The generated file is plain markdown that Claude reads on session start.
func WriteMultiRepoParentSettings ¶ added in v1.9.31
WriteMultiRepoParentSettings writes a .claude/settings.json to parentDir containing the intersection of all child projects' permissions.allow and the union of permissions.deny and permissions.ask.
Intersection for allow ensures no command is auto-approved in the parent that would be unsafe in any child project. Union for deny/ask ensures safety boundaries from any child are always enforced.
func WriteOpenCodeGlobalMCP ¶ added in v1.9.59
WriteOpenCodeGlobalMCP writes catalog MCPs to ~/.config/opencode/opencode.json. Preserves other JSON keys already present in the file.
func WriteOpenCodeProjectMCP ¶ added in v1.9.59
WriteOpenCodeProjectMCP writes catalog MCPs to <project>/opencode.json. OpenCode's local config uses {"mcp": {"name": {"type":"local","command":[...]}}} format.
func WriteSessionIDLifecycleEvent ¶ added in v0.28.0
func WriteSessionIDLifecycleEvent(event SessionIDLifecycleEvent) error
WriteSessionIDLifecycleEvent appends a single JSONL event.
func WriteSessionLifecycleEvent ¶ added in v1.9.29
func WriteSessionLifecycleEvent(ev SessionLifecycleEvent) error
WriteSessionLifecycleEvent appends a single JSONL row.
func WriteStatusEvent ¶ added in v0.18.0
func WriteStatusEvent(event StatusEvent) error
WriteStatusEvent atomically writes a status event to the events directory. Uses tmp file + rename to avoid partial reads by watchers.
func WriteUserMCP ¶ added in v0.8.69
WriteUserMCP writes MCPs to ~/.claude.json (ROOT config) Uses socket proxies if pool is running, otherwise falls back to stdio WARNING: MCPs written here affect ALL Claude sessions regardless of profile!
func ZoxideAvailable ¶ added in v1.7.52
func ZoxideAvailable() bool
ZoxideAvailable reports whether the zoxide binary is reachable on PATH.
func ZoxideQuery ¶ added in v1.7.52
ZoxideQuery returns matching directory paths from zoxide's database, ordered by frecency. An empty query returns the full list. Returns an empty slice (not an error) when zoxide has no matches for the query.
Types ¶
type AnalyticsDisplaySettings ¶ added in v0.8.53
type AnalyticsDisplaySettings struct {
// ShowContextBar shows the context window usage bar (default: true)
ShowContextBar *bool `toml:"show_context_bar,omitempty"`
// ShowTokens shows the token breakdown (In/Out/Cache/Total) (default: false)
ShowTokens *bool `toml:"show_tokens,omitempty"`
// ShowSessionInfo shows duration, turns, start time (default: false)
ShowSessionInfo *bool `toml:"show_session_info,omitempty"`
// ShowTools shows the top tool calls (default: false)
ShowTools *bool `toml:"show_tools,omitempty"`
// ShowCost shows the estimated cost (default: false)
ShowCost *bool `toml:"show_cost,omitempty"`
}
AnalyticsDisplaySettings configures which analytics sections to display All settings use pointers to distinguish "not set" from "explicitly false"
func (*AnalyticsDisplaySettings) GetShowContextBar ¶ added in v0.8.53
func (a *AnalyticsDisplaySettings) GetShowContextBar() bool
GetShowContextBar returns whether to show context bar, defaulting to true
func (*AnalyticsDisplaySettings) GetShowCost ¶ added in v0.8.53
func (a *AnalyticsDisplaySettings) GetShowCost() bool
GetShowCost returns whether to show cost estimate, defaulting to false
func (*AnalyticsDisplaySettings) GetShowSessionInfo ¶ added in v0.8.53
func (a *AnalyticsDisplaySettings) GetShowSessionInfo() bool
GetShowSessionInfo returns whether to show session info, defaulting to false
func (*AnalyticsDisplaySettings) GetShowTokens ¶ added in v0.8.53
func (a *AnalyticsDisplaySettings) GetShowTokens() bool
GetShowTokens returns whether to show token breakdown, defaulting to false
func (*AnalyticsDisplaySettings) GetShowTools ¶ added in v0.8.53
func (a *AnalyticsDisplaySettings) GetShowTools() bool
GetShowTools returns whether to show tool calls, defaulting to false
type BillingBlock ¶ added in v0.8.27
type BillingBlock struct {
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
TokensUsed int `json:"tokens_used"`
IsActive bool `json:"is_active"`
}
BillingBlock represents a 5-hour billing window
func CalculateBillingBlocks ¶ added in v0.8.27
func CalculateBillingBlocks(timestamps []time.Time, windowSize time.Duration) []BillingBlock
CalculateBillingBlocks groups timestamps into billing windows. Claude Code API bills in 5-hour windows. Each block represents a billing period. Timestamps are sorted chronologically and grouped - a new block starts when a timestamp exceeds the windowSize from the current block's start time. The last block is marked as "active" if it's still within the window from now.
type BudgetSettings ¶ added in v0.26.4
type BudgetSettings struct {
DailyLimit float64 `toml:"daily_limit,omitzero"`
WeeklyLimit float64 `toml:"weekly_limit,omitzero"`
MonthlyLimit float64 `toml:"monthly_limit,omitzero"`
Groups map[string]GroupBudget `toml:"groups,omitempty"`
Sessions map[string]SessionBudget `toml:"sessions,omitempty"`
}
type ClaudeConfig ¶ added in v0.4.1
type ClaudeConfig struct {
Projects map[string]ClaudeProject `json:"projects"`
}
ClaudeConfig represents the structure of .claude.json
type ClaudeOptions ¶ added in v0.8.39
type ClaudeOptions struct {
// SessionMode: "new" (default), "continue" (-c), or "resume" (-r)
SessionMode string `json:"session_mode,omitempty"`
// ResumeSessionID is the session ID for -r flag (only when SessionMode="resume")
ResumeSessionID string `json:"resume_session_id,omitempty"`
// Model overrides the Claude model for this session. Aliases like "sonnet",
// "opus", and "haiku" let Claude Code resolve the latest version; full
// model IDs pin a specific version.
Model string `json:"model,omitempty"`
// SkipPermissions adds --dangerously-skip-permissions flag
SkipPermissions bool `json:"skip_permissions,omitempty"`
// AllowSkipPermissions adds --allow-dangerously-skip-permissions flag
// Only used when SkipPermissions is false (SkipPermissions takes precedence)
AllowSkipPermissions bool `json:"allow_skip_permissions,omitempty"`
// AutoMode adds --permission-mode auto flag
// Uses a classifier model to auto-approve safe operations while blocking risky ones.
// Only used when SkipPermissions is false (SkipPermissions takes precedence).
AutoMode bool `json:"auto_mode,omitempty"`
// UseChrome adds --chrome flag
UseChrome bool `json:"use_chrome,omitempty"`
// UseTeammateMode adds --teammate-mode tmux flag
UseTeammateMode bool `json:"use_teammate_mode,omitempty"`
// Transient fields for worktree fork (not persisted)
WorkDir string `json:"-"`
WorktreePath string `json:"-"`
WorktreeRepoRoot string `json:"-"`
WorktreeBranch string `json:"-"`
}
ClaudeOptions holds launch options for Claude Code sessions
func NewClaudeOptions ¶ added in v0.8.39
func NewClaudeOptions(config *UserConfig) *ClaudeOptions
NewClaudeOptions creates ClaudeOptions with defaults from config
func UnmarshalClaudeOptions ¶ added in v0.8.39
func UnmarshalClaudeOptions(data json.RawMessage) (*ClaudeOptions, error)
UnmarshalClaudeOptions deserializes ClaudeOptions from JSON wrapper
func (*ClaudeOptions) ToArgs ¶ added in v0.8.39
func (o *ClaudeOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
func (*ClaudeOptions) ToArgsForFork ¶ added in v0.8.39
func (o *ClaudeOptions) ToArgsForFork() []string
ToArgsForFork returns arguments suitable for fork resume command Fork always uses --resume internally, so session mode flags are not included
func (*ClaudeOptions) ToolName ¶ added in v0.8.39
func (o *ClaudeOptions) ToolName() string
ToolName returns "claude"
type ClaudeProject ¶ added in v0.4.1
type ClaudeProject struct {
LastSessionId string `json:"lastSessionId"`
}
ClaudeProject represents a project entry in Claude's config
type ClaudeSettings ¶ added in v0.4.1
type ClaudeSettings struct {
// Command is the Claude CLI command or alias to use (e.g., "claude", "cdw", "cdp")
// Default: "claude"
// This allows using shell aliases that set CLAUDE_CONFIG_DIR automatically
Command string `toml:"command,omitempty"`
// ConfigDir is the path to Claude's config directory
// Default: ~/.claude (or CLAUDE_CONFIG_DIR env var)
ConfigDir string `toml:"config_dir,omitempty"`
// DangerousMode enables --dangerously-skip-permissions flag for Claude sessions
// Default: true (nil = use default true, explicitly set false to disable)
// Power users typically want this enabled for faster iteration
DangerousMode *bool `toml:"dangerous_mode,omitempty"`
// AllowDangerousMode enables --allow-dangerously-skip-permissions flag
// This unlocks bypass as an option without activating it by default.
// Ignored when dangerous_mode is true (the stronger flag takes precedence).
// Default: false
AllowDangerousMode bool `toml:"allow_dangerous_mode,omitempty"`
// AutoMode enables --permission-mode auto flag for Claude sessions
// A classifier model reviews commands before they run, blocking scope escalation
// and hostile-content-driven actions while letting routine work proceed without prompts.
// Ignored when dangerous_mode is true (the stronger flag takes precedence).
// Default: false
AutoMode bool `toml:"auto_mode,omitempty"`
// ExtraArgs are user-supplied Claude CLI flags used as the New Session
// dialog default. They are persisted as discrete TOML array entries and
// copied to Instance.ExtraArgs when a Claude session is created.
ExtraArgs []string `toml:"extra_args,omitempty"`
// DefaultModel is the model to preselect for new Claude sessions
// (e.g., "claude-opus-4-7"). Mirrors [gemini]/[opencode]/[copilot]
// default_model. When empty, the dialog leaves the model unset and Claude
// Code falls back to its own default (#1172).
DefaultModel string `toml:"default_model,omitempty"`
// UseChrome enables --chrome by default for Claude sessions.
UseChrome bool `toml:"use_chrome,omitempty"`
// UseTeammateMode enables --teammate-mode tmux by default for Claude sessions.
UseTeammateMode bool `toml:"use_teammate_mode,omitempty"`
// EnvFile is a .env file specific to Claude sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file,omitempty"`
// HooksEnabled enables Claude Code hooks for real-time status detection.
// When enabled, agent-deck uses lifecycle hooks (SessionStart, Stop, etc.)
// for instant, deterministic status updates instead of polling tmux content.
// Default: true (nil = use default true, set false to disable)
HooksEnabled *bool `toml:"hooks_enabled,omitempty"`
// AutoResumeSummary auto-presses Enter on Claude's "Resume from summary"
// picker that appears after `claude --resume` on long-running sessions
// (>~250k tokens). Critical for unattended conductors which would
// otherwise sit frozen on the picker forever (closes #67).
// Default: true (nil = use default true, set false to disable).
AutoResumeSummary *bool `toml:"auto_resume_summary,omitempty"`
// VimMode tells agent-deck the inner Claude Code prompt uses vim keybindings
// ("editorMode": "vim"). When true, every message send guarantees the
// composer is in insert mode (Escape + `i`) before delivering text/Enter, so
// a message sent while the prompt sits in vim NORMAL mode (the default state
// after a turn finishes) actually submits instead of being typed-but-unsent
// (issue #1264). Off by default — only enable for sessions running Claude
// Code with vim editor mode. Other tools and non-vim Claude are unaffected.
VimMode bool `toml:"vim_mode,omitempty"`
}
ClaudeSettings defines Claude Code configuration
func (*ClaudeSettings) GetAutoResumeSummary ¶ added in v1.9.19
func (c *ClaudeSettings) GetAutoResumeSummary() bool
GetAutoResumeSummary returns whether the "Resume from summary" picker is auto-confirmed on session restart, defaulting to true. Conductors and any other unattended session runner depend on this — without it, a single claude --resume on a >250k-token session leaves the session frozen on the picker screen forever.
func (*ClaudeSettings) GetDangerousMode ¶ added in v0.8.76
func (c *ClaudeSettings) GetDangerousMode() bool
GetDangerousMode returns whether dangerous mode is enabled, defaulting to true Power users (the primary audience) typically want this enabled for faster iteration
func (*ClaudeSettings) GetHooksEnabled ¶ added in v0.16.0
func (c *ClaudeSettings) GetHooksEnabled() bool
GetHooksEnabled returns whether Claude Code hooks are enabled, defaulting to true
func (*ClaudeSettings) GetVimMode ¶ added in v1.9.47
func (c *ClaudeSettings) GetVimMode() bool
GetVimMode reports whether vim-mode insert-guard sends are enabled. Off by default (issue #1264).
type CodexOptions ¶ added in v0.10.18
type CodexOptions struct {
// Model overrides the Codex model for this session (for example, "gpt-5").
Model string `json:"model,omitempty"`
// YoloMode enables --yolo flag (bypass approvals and sandbox)
// nil = inherit from global config, true/false = explicit override
YoloMode *bool `json:"yolo_mode,omitempty"`
}
CodexOptions holds launch options for Codex CLI sessions
func NewCodexOptions ¶ added in v0.10.18
func NewCodexOptions(config *UserConfig) *CodexOptions
NewCodexOptions creates CodexOptions with defaults from global config
func UnmarshalCodexOptions ¶ added in v0.10.18
func UnmarshalCodexOptions(data json.RawMessage) (*CodexOptions, error)
UnmarshalCodexOptions deserializes CodexOptions from JSON wrapper
func (*CodexOptions) ToArgs ¶ added in v0.10.18
func (o *CodexOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
func (*CodexOptions) ToolName ¶ added in v0.10.18
func (o *CodexOptions) ToolName() string
ToolName returns "codex"
type CodexSettings ¶ added in v0.10.18
type CodexSettings struct {
// Command is the Codex CLI command or alias to use (e.g., "codex", "codex-v2")
// Default: "codex"
Command string `toml:"command,omitempty"`
// ConfigDir is the path to Codex home directory.
// Default: ~/.codex (or CODEX_HOME env var)
ConfigDir string `toml:"config_dir,omitempty"`
// YoloMode enables --yolo flag for Codex sessions (bypass approvals and sandbox)
// Default: false
YoloMode bool `toml:"yolo_mode,omitempty"`
// EnvFile is a .env file specific to Codex sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file,omitempty"`
}
CodexSettings defines Codex CLI configuration
type CompletionCycler ¶ added in v0.8.86
type CompletionCycler struct {
// contains filtered or unexported fields
}
CompletionCycler manages the state of directory autocomplete.
func (*CompletionCycler) IsActive ¶ added in v0.8.86
func (c *CompletionCycler) IsActive() bool
IsActive returns true if the cycler has active matches.
func (*CompletionCycler) Next ¶ added in v0.8.86
func (c *CompletionCycler) Next() string
Next returns the next match in the cycle. Wraps around to the beginning if the end is reached.
func (*CompletionCycler) Reset ¶ added in v0.8.86
func (c *CompletionCycler) Reset()
Reset clears the cycler state.
func (*CompletionCycler) SetMatches ¶ added in v0.8.86
func (c *CompletionCycler) SetMatches(matches []string)
SetMatches sets the matches for the cycler and resets the index.
type CompletionLedgerEntry ¶ added in v1.10.9
type CompletionLedgerEntry struct {
ChildID string `json:"child_id"`
Profile string `json:"profile"`
Title string `json:"title,omitempty"`
Status string `json:"status"` // "ok" | "fail"
Summary string `json:"summary,omitempty"`
FinishedAt time.Time `json:"finished_at"`
}
CompletionLedgerEntry is the durable, non-destructive last-known completion for a child session. Unlike the task-worker CompletionRecord (whose presence makes the daemon stand down from poll-inference; see emitDoneSignals' CompletionRecordExists guard), the ledger is purely informational: it records the most recent asserted completion so a parent can query "which of my fleet finished" without consuming any delivery event. Last-wins per child.
func ReadLedgerEntry ¶ added in v1.10.9
func ReadLedgerEntry(childID string) (CompletionLedgerEntry, bool)
ReadLedgerEntry returns the last-known completion for a child, if any. The read is non-destructive — checking from a parent never consumes a delivery event the conductor or another chat relies on.
type CompletionRecord ¶ added in v1.9.43
type CompletionRecord struct {
ChildID string `json:"child_id"`
Profile string `json:"profile"`
Title string `json:"title,omitempty"`
Status string `json:"status"` // "" = pending; "ok" | "fail" = finished
Summary string `json:"summary,omitempty"`
ExitCode int `json:"exit_code"`
CreatedAt time.Time `json:"created_at"`
FinishedAt time.Time `json:"finished_at,omitzero"`
Acked bool `json:"acked"`
}
CompletionRecord is the durable, atomically-written record a task-worker wrapper produces. A record with an empty Status is a "claim" written when the worker starts (so the daemon suppresses poll-inference for the whole run, closing the race against the worker's own Stop hook); it is finalized to ok|fail on exit.
func LoadCompletionRecords ¶ added in v1.9.43
func LoadCompletionRecords(profile string) ([]CompletionRecord, error)
LoadCompletionRecords returns every record for the given profile.
func RunTaskWorker ¶ added in v1.9.43
func RunTaskWorker(childID, profile, title string, cmd *exec.Cmd) (CompletionRecord, error)
RunTaskWorker runs the one-shot worker command to completion. It claims the child first (so the daemon suppresses poll-inference for the whole run), blocks on the kernel exit via cmd.Run/Wait (exactly-once by construction), derives the completion from captured output + exit code, and writes the finalized durable record. It does NOT itself wake the parent — that is the caller's (DeliverCompletion) so the same record can be replayed if the parent is down. The worker's own non-zero exit is recorded, not returned as an error; a returned error means the wrapper itself failed (record write, etc.).
type ConductorAgentSpec ¶ added in v0.26.4
type ConductorAgentSpec struct {
Agent string
DisplayName string
DefaultCommand string
InstructionsFileName string
SupportsClearOnCompact bool
}
ConductorAgentSpec describes conductor-specific behavior for an agent runtime.
func GetConductorAgentSpec ¶ added in v0.26.4
func GetConductorAgentSpec(agent string) (ConductorAgentSpec, error)
GetConductorAgentSpec returns the normalized spec for a supported conductor agent.
type ConductorClaudeSettings ¶ added in v1.5.4
type ConductorClaudeSettings struct {
// ConfigDir overrides [claude].config_dir for this conductor only.
ConfigDir string `toml:"config_dir,omitempty"`
// EnvFile is sourced before claude exec for this conductor.
// Matches CFG-03 semantics — missing file logs a warning, does not block.
EnvFile string `toml:"env_file,omitempty"`
// Command overrides [claude].command for this conductor only.
// Mirrors GroupClaudeSettings.Command; conductor beats group.
Command string `toml:"command,omitempty"`
// Model is the model default for this conductor's sessions. An
// explicit per-session model wins; empty falls through (#1172).
Model string `toml:"model,omitempty"`
// Env is an inline env map exported AFTER the env_file source and
// AFTER the group env map (conductor wins per key on conflict).
Env map[string]string `toml:"env,omitempty"`
// Skills lists declarative skill-loadout entries ("<source>/<name>")
// unioned on top of the group floor for this conductor's sessions.
Skills []string `toml:"skills,omitempty"`
// Plugins lists [plugins.X] catalog keys unioned on top of the group floor.
Plugins []string `toml:"plugins,omitempty"`
// MCPs lists [mcps.X] catalog names unioned on top of the group
// floor. Same semantics as Skills.
MCPs []string `toml:"mcps,omitempty"`
}
ConductorClaudeSettings defines conductor-specific Claude overrides. Semantics mirror GroupClaudeSettings — ExpandPath is applied on read via GetConductorClaudeConfigDir; env_file resolution is deferred to the spawn builder (resolvePath handles path expansion at use).
type ConductorDirMigrateAction ¶ added in v1.9.63
type ConductorDirMigrateAction struct {
Name string // entry name (conductor name or base file)
IsHome bool // conductor home (dir with meta.json) vs base file/symlink
// Action is one of:
// "move" dest absent → copy then remove source
// "merge" dest exists + --force, safe → merge (dest wins per-file).
// The conductor's meta.json is always preserved (a differing
// one is a reject-conflict, never a merge), but for NON-meta
// files the merge is destination-wins: a source state.json /
// CLAUDE.md that differs from the destination's is dropped,
// and its source copy is removed with the rest of the source.
// "skip-exists" dest exists, no --force → left in place (blocks the migration)
// "skip-transient" runtime log/temp → ignored (regenerated at the new base)
// "reject-conflict" dest exists + --force, but merging would clobber the
// source's durable meta.json → refused (blocks the migration)
Action string
Conflict bool // a destination already existed (preserved)
Reason string // why a reject-conflict was refused (empty otherwise)
}
ConductorDirMigrateAction records the disposition of a single base-level entry.
type ConductorDirMigrateOptions ¶ added in v1.9.63
type ConductorDirMigrateOptions struct {
// Target is the destination base dir (tilde/$VAR expanded by the migrator).
Target string
// From optionally overrides the auto-detected source base.
From string
// Apply performs the move; when false the migration is a dry-run that
// mutates nothing.
Apply bool
// Force merges into an existing destination per-file (destination wins on
// per-file conflicts) instead of skipping it.
Force bool
}
ConductorDirMigrateOptions configures a conductor-dir relocation.
type ConductorDirMigrateResult ¶ added in v1.9.63
type ConductorDirMigrateResult struct {
DryRun bool
Source string
Target string
Actions []ConductorDirMigrateAction
Conductors []string // conductor homes present in target afterward
ConfigWritten bool
// Refused is true when the migration was aborted because the plan was not
// clean — a home whose destination exists without --force, or a conductor
// whose durable record would be clobbered. When Refused, nothing was mutated
// and [conductor].dir was NOT repointed (the flip is all-or-nothing).
Refused bool
Blockers []string // human-readable reasons the migration was refused
// SourceRemovalWarnings records sources that could not be removed AFTER the
// config was already committed (non-fatal: the durable record exists at the
// committed target; the leftover is a harmless duplicate at the old base).
SourceRemovalWarnings []string
BridgeReinstalled bool
}
ConductorDirMigrateResult summarizes a relocation for reporting.
func MigrateConductorDir ¶ added in v1.9.63
func MigrateConductorDir(opts ConductorDirMigrateOptions) (*ConductorDirMigrateResult, error)
MigrateConductorDir relocates the conductor base from its current/source location to Target as one explicit, transactional relocation. The mutating path follows copy → verify → commit-config → remove-source so a failure before the config commit leaves every source intact (fully recoverable) and a failure after leaves the verified durable record at the committed target — there is never a window where a conductor's only meta.json exists at a half-applied target. The whole operation runs under the conductor base lock so no concurrent setup/meta write can be stranded or deleted.
- Plan: classify every base-level entry (move/merge/skip/reject) with NO mutation. If any home cannot move cleanly (destination exists without --force, or a --force merge would clobber the source's durable meta.json), refuse the WHOLE migration and mutate nothing — the [conductor].dir flip is all-or-nothing.
- Copy every entry source→target (non-destructive, no source removal yet).
- Verify each migrated home's meta.json landed readable at the target.
- Commit [conductor].dir = target.
- Remove the migrated sources.
- Reconcile path-baked artifacts: re-render heartbeat.sh per conductor and reinstall base bridge.py.
Daemon reloads (launchctl/systemctl) are deliberately NOT done here — they belong to the CLI handler so this function stays unit-testable without a service manager. The returned Conductors list is the reconcile/reload set.
A dry-run (Apply=false) builds and reports the full plan — including every home it would skip, overwrite, or reject — and changes nothing.
type ConductorHermesSettings ¶ added in v1.9.47
type ConductorHermesSettings struct {
Command string `toml:"command,omitempty"`
EnvFile string `toml:"env_file,omitempty"`
YoloMode bool `toml:"yolo_mode,omitempty"`
GatewayURL string `toml:"gateway_url,omitempty"`
DashboardURL string `toml:"dashboard_url,omitempty"`
APITokenEnv string `toml:"api_token_env,omitempty"`
}
ConductorHermesSettings defines conductor-specific Hermes overrides.
type ConductorMeta ¶ added in v0.12.0
type ConductorMeta struct {
Name string `json:"name"`
Agent string `json:"agent,omitempty"`
Profile string `json:"profile"`
HeartbeatEnabled bool `json:"heartbeat_enabled"`
HeartbeatInterval int `json:"heartbeat_interval"` // 0 = use global default
Description string `json:"description,omitempty"`
CreatedAt string `json:"created_at"`
// ClearOnCompact blocks Claude's auto-compaction and sends /clear instead.
// When context fills up (~95%), Claude normally summarizes prior conversation (lossy).
// With this enabled, agent-deck blocks compaction and clears context entirely,
// relying on CLAUDE.md and conductor state for continuity.
// Default: true (nil = use default true via GetClearOnCompact)
ClearOnCompact *bool `json:"clear_on_compact,omitempty"`
// Env holds inline environment variables for the conductor session.
// These are exported before the conductor command launches.
Env map[string]string `json:"env,omitempty"`
// EnvFile is a path to a .env file to source before the conductor command.
// Supports ~ and $VAR expansion.
EnvFile string `json:"env_file,omitempty"`
// HeartbeatIdleMinutes is the minutes of inactivity before pausing heartbeats.
// 0 or negative = disabled (never pause). Positive = number of minutes.
HeartbeatIdleMinutes int `json:"heartbeat_idle_minutes"`
}
ConductorMeta holds metadata for a named conductor instance
func ListConductors ¶ added in v0.12.0
func ListConductors() ([]ConductorMeta, error)
ListConductors scans all conductor directories that have meta.json
func ListConductorsForProfile ¶ added in v0.12.0
func ListConductorsForProfile(profile string) ([]ConductorMeta, error)
ListConductorsForProfile returns conductors belonging to a specific profile
func LoadConductorMeta ¶ added in v0.12.0
func LoadConductorMeta(name string) (*ConductorMeta, error)
LoadConductorMeta reads meta.json for a named conductor
func (*ConductorMeta) GetAgent ¶ added in v0.26.4
func (m *ConductorMeta) GetAgent() string
GetAgent returns the normalized conductor agent, defaulting to Claude.
func (*ConductorMeta) GetClearOnCompact ¶ added in v0.19.15
func (m *ConductorMeta) GetClearOnCompact() bool
GetClearOnCompact returns whether to block compaction and send /clear instead, defaulting to true. For Hermes conductors, this enables context clearing on compaction (similar to Claude), as Hermes does not perform automatic summarization like Claude does.
func (*ConductorMeta) GetHeartbeatIdleMinutes ¶ added in v1.9.20
func (m *ConductorMeta) GetHeartbeatIdleMinutes() int
GetHeartbeatIdleMinutes returns the heartbeat idle threshold in minutes. Returns 0 when disabled (value is 0 or negative). Returns the configured value when positive.
type ConductorOverrides ¶ added in v1.5.4
type ConductorOverrides struct {
// Claude defines Claude Code overrides for a specific conductor.
Claude ConductorClaudeSettings `toml:"claude,omitempty"`
// Hermes defines Hermes overrides for a specific conductor.
Hermes ConductorHermesSettings `toml:"hermes,omitempty"`
}
ConductorOverrides defines per-conductor configuration overrides. Mirrors GroupSettings — conductors are first-class entities keyed by conductor name (derived from Instance.Title via strings.TrimPrefix at the call site, same pattern as env.go getConductorEnv).
Named ConductorOverrides (not ConductorSettings) to avoid collision with the pre-existing global [conductor] meta-agent orchestration block declared in conductor.go:49 (heartbeat, telegram, slack, discord). Closes issue #602.
type ConductorSettings ¶ added in v0.12.0
type ConductorSettings struct {
// Deprecated: Enabled is retained only so pre-#1361 configs that still carry
// `enabled = true/false` continue to load without error. The flag is no
// longer read anywhere — the conductor system's active state is derived from
// filesystem presence via ConductorSystemActive(). Setup no longer writes
// this field. TOML decoding ignores unknown keys, but keeping the field
// avoids a "field not found" surprise for anyone round-tripping old configs.
Enabled bool `toml:"enabled,omitempty"`
// HeartbeatInterval is the interval in minutes between heartbeat checks
// nil/absent = disabled (preserves pre-*int behavior), 0 = disabled, >0 = configured
HeartbeatInterval *int `toml:"heartbeat_interval,omitempty"`
// Profiles is the list of agent-deck profiles to manage
// Kept for backward compat but ignored after migration to meta.json-based discovery
Profiles []string `toml:"profiles,omitempty"`
// Telegram defines Telegram bot integration settings
Telegram TelegramSettings `toml:"telegram,omitempty"`
// Slack defines Slack bot integration settings
Slack SlackSettings `toml:"slack,omitempty"`
// Discord defines Discord bot integration settings
Discord DiscordSettings `toml:"discord,omitempty"`
// Dir overrides the base conductor directory. Empty = default
// (<data-dir>/conductor with legacy ~/.agent-deck/conductor fallback).
// Tilde and $VAR are expanded.
//
// The rendered heartbeat.sh honors this override and is auto-refreshed by
// MigrateConductorHeartbeatScripts on the next conductor command (list /
// status / setup / teardown), so the script content reconciles on its own.
// What does NOT self-heal is the daemon: the launchd heartbeat plist (and
// the Linux systemd unit) bakes absolute script/log paths at install time
// and is regenerated only by 'conductor setup'. After changing dir, re-run
// 'conductor setup' per conductor to regenerate + reload the daemon (or use
// 'conductor migrate-dir'). The bridge daemon similarly freezes
// AGENT_DECK_CONDUCTOR_DIR at install time.
Dir string `toml:"dir,omitempty"`
}
ConductorSettings defines conductor (meta-agent orchestration) configuration
func GetConductorSettings ¶ added in v0.12.0
func GetConductorSettings() ConductorSettings
GetConductorSettings loads and returns conductor settings from config
func (*ConductorSettings) GetHeartbeatInterval ¶ added in v0.12.0
func (c *ConductorSettings) GetHeartbeatInterval() int
GetHeartbeatInterval returns the heartbeat interval in minutes. nil = disabled (field absent), 0 = disabled, negative = default (15), positive = configured. TODO(breaking): collapse negative→disabled once a major version allows it.
func (*ConductorSettings) GetProfiles ¶ added in v0.12.0
func (c *ConductorSettings) GetProfiles() []string
GetProfiles returns the configured profiles, defaulting to ["default"]
type Config ¶ added in v0.3.0
type Config struct {
// DefaultProfile is the profile to use when none is specified
DefaultProfile string `json:"default_profile"`
// LastUsed is the most recently used profile (for future use)
LastUsed string `json:"last_used,omitempty"`
// Version tracks config format for future migrations
Version int `json:"version"`
}
Config represents the global agent-deck configuration
func LoadConfig ¶ added in v0.3.0
LoadConfig loads the global configuration
type ContentBuffer ¶ added in v0.10.0
type ContentBuffer struct {
// contains filtered or unexported fields
}
ContentBuffer stores full content and a lowercased copy for fast search. It is safe for concurrent reads and append-only writes.
func (*ContentBuffer) Append ¶ added in v0.10.0
func (b *ContentBuffer) Append(data []byte)
func (*ContentBuffer) CopyData ¶ added in v0.10.0
func (b *ContentBuffer) CopyData() []byte
func (*ContentBuffer) Size ¶ added in v0.10.5
func (b *ContentBuffer) Size() int64
Size returns the total memory used by this buffer (data + lowercased copy)
func (*ContentBuffer) With ¶ added in v0.10.0
func (b *ContentBuffer) With(fn func(data, lower []byte))
type CopilotOptions ¶ added in v1.7.26
type CopilotOptions struct {
// SessionMode: "new" (default) or "resume" (--resume).
// When "resume" and ResumeSessionID is empty, Copilot CLI shows its
// session picker; when set, agent-deck passes the ID through as
// --resume <id>.
SessionMode string `json:"session_mode,omitempty"`
// ResumeSessionID is the Copilot session ID for --resume (only used
// when SessionMode == "resume").
ResumeSessionID string `json:"resume_session_id,omitempty"`
// Model overrides the default Copilot model (e.g., "claude-opus-4.6",
// "gpt-5.2"). Passed as --model <value>.
Model string `json:"model,omitempty"`
// AllowAll enables --allow-all (equivalent to --allow-all-tools
// --allow-all-paths --allow-all-urls). Required for non-interactive
// scripting scenarios.
AllowAll bool `json:"allow_all,omitempty"`
}
CopilotOptions holds launch options for GitHub Copilot CLI sessions (the standalone `copilot` binary from @github/copilot, not the older `gh copilot` extension).
func NewCopilotOptions ¶ added in v1.7.26
func NewCopilotOptions(config *UserConfig) *CopilotOptions
NewCopilotOptions creates CopilotOptions with defaults from config
func UnmarshalCopilotOptions ¶ added in v1.7.26
func UnmarshalCopilotOptions(data json.RawMessage) (*CopilotOptions, error)
UnmarshalCopilotOptions deserializes CopilotOptions from JSON wrapper
func (*CopilotOptions) ToArgs ¶ added in v1.7.26
func (o *CopilotOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
func (*CopilotOptions) ToolName ¶ added in v1.7.26
func (o *CopilotOptions) ToolName() string
ToolName returns "copilot"
type CopilotSettings ¶ added in v1.7.26
type CopilotSettings struct {
// EnvFile is a .env file specific to Copilot sessions (sourced before
// the `copilot` command runs, like [gemini].env_file). Optional.
EnvFile string `toml:"env_file,omitempty"`
// Command overrides the default binary/invocation for Copilot sessions.
// Supports flags (e.g., "copilot --custom-flag"). Default: "copilot"
Command string `toml:"command,omitempty"`
// DefaultModel sets the Copilot model for new sessions (e.g., "claude-opus-4.6",
// "gpt-5.2"). Passed as --model <value>. Can be overridden per-session.
DefaultModel string `toml:"default_model,omitempty"`
// AllowAll enables --allow-all by default for new sessions (equivalent to
// --allow-all-tools --allow-all-paths --allow-all-urls). Can be overridden
// per-session.
AllowAll bool `toml:"allow_all,omitempty"`
}
CopilotSettings defines GitHub Copilot CLI configuration (Issue #556). Binary: `copilot` from @github/copilot (GA 2026-02-25). Doc: https://docs.github.com/en/copilot/concepts/agents/about-copilot-cli
type CostsSettings ¶ added in v0.26.4
type CostsSettings struct {
Currency string `toml:"currency,omitempty"`
Timezone string `toml:"timezone,omitempty"`
RetentionDays int `toml:"retention_days,omitzero"`
// CostLineTemplate overrides the home status-bar cost segment.
// Three-state pointer: nil falls through to the next layer
// (profile -> global -> hardcoded); explicit empty string disables.
CostLineTemplate *string `toml:"cost_line_template,omitempty"`
// CostLineHideWhenZero hides the segment when every recognized variable
// in the active template renders to $0.00. Three-state pointer; default
// is true (preserves the legacy "no events, no segment" behavior).
CostLineHideWhenZero *bool `toml:"cost_line_hide_when_zero,omitempty"`
Budgets BudgetSettings `toml:"budgets,omitempty"`
Pricing PricingSettings `toml:"pricing,omitempty"`
}
CostsSettings configures cost tracking, budgets, and pricing overrides.
func (CostsSettings) GetRetentionDays ¶ added in v0.26.4
func (c CostsSettings) GetRetentionDays() int
func (CostsSettings) GetTimezone ¶ added in v0.26.4
func (c CostsSettings) GetTimezone() string
type CrushOptions ¶ added in v1.9.14
type CrushOptions struct {
// YoloMode enables --yolo flag (auto-accept all permission prompts).
// nil = inherit from config, true/false = explicit override.
YoloMode *bool `json:"yolo_mode,omitempty"`
// ResumeSessionID is the crush session ID for --session/-s flag.
// Empty means start a fresh session.
ResumeSessionID string `json:"resume_session_id,omitempty"`
// ContinueLast maps to --continue/-C (resume the most recent session).
// Mutually exclusive with ResumeSessionID at the crush CLI level; if
// both are set, ResumeSessionID wins (it is the more specific signal).
ContinueLast bool `json:"continue_last,omitempty"`
}
CrushOptions holds launch options for charmbracelet/crush CLI sessions (Issue #940). Binary: `crush` from github.com/charmbracelet/crush.
func NewCrushOptions ¶ added in v1.9.14
func NewCrushOptions(config *UserConfig) *CrushOptions
NewCrushOptions creates CrushOptions with defaults from global config.
func UnmarshalCrushOptions ¶ added in v1.9.14
func UnmarshalCrushOptions(data json.RawMessage) (*CrushOptions, error)
UnmarshalCrushOptions deserializes CrushOptions from JSON wrapper.
func (*CrushOptions) ToArgs ¶ added in v1.9.14
func (o *CrushOptions) ToArgs() []string
ToArgs returns command-line arguments based on options. Ordering matches the crush CLI flag groups (session first, then yolo).
func (*CrushOptions) ToolName ¶ added in v1.9.14
func (o *CrushOptions) ToolName() string
ToolName returns "crush"
type CrushSettings ¶ added in v1.9.14
type CrushSettings struct {
// Command overrides the default binary/invocation for Crush sessions.
// Supports flags (e.g., "crush --debug"). Default: "crush"
Command string `toml:"command,omitempty"`
// EnvFile is a .env file specific to Crush sessions (sourced before
// the `crush` command runs, like [gemini].env_file). Optional.
EnvFile string `toml:"env_file,omitempty"`
// YoloMode enables --yolo flag for Crush sessions (auto-accept all
// permission prompts). Default: false
YoloMode bool `toml:"yolo_mode,omitempty"`
}
CrushSettings defines charmbracelet/crush CLI configuration (Issue #940). Binary: `crush` from github.com/charmbracelet/crush. Interactive TUI. Key flags: --yolo, --session/-s <id>, --continue/-C, --cwd, --debug.
type DeadLetterSink ¶ added in v1.9.44
type DeadLetterSink struct {
// contains filtered or unexported fields
}
DeadLetterSink tracks per-child unresolved attempt counts and emits exactly one missed-log line when a record crosses MaxUnresolvedAttempts. Concurrency- safe. The missed-log path is injectable for tests.
func NewDeadLetterSink ¶ added in v1.9.44
func NewDeadLetterSink(missedPath string) *DeadLetterSink
NewDeadLetterSink builds a sink that writes its single missed line to missedPath (use the notifier-missed.log path in production).
func (*DeadLetterSink) RecordUnresolvable ¶ added in v1.9.44
func (s *DeadLetterSink) RecordUnresolvable(event TransitionNotificationEvent) bool
RecordUnresolvable accounts one failed attempt to resolve event's target. It returns true exactly once — on the attempt that crosses MaxUnresolvedAttempts — at which point the record is parked in the dead-letter store and a single missed line is written. Further calls for the same child are no-ops (no runaway, no second log line).
type DiscordSettings ¶ added in v0.20.1
type DiscordSettings struct {
// BotToken is the Discord bot token from the Developer Portal
BotToken string `toml:"bot_token,omitempty"`
// GuildID is the Discord server (guild) where the bot operates
GuildID int64 `toml:"guild_id,omitzero"`
// ChannelID is the Discord channel where the bot listens and posts
ChannelID int64 `toml:"channel_id,omitzero"`
// UserID is the authorized Discord user ID
UserID int64 `toml:"user_id,omitzero"`
// ListenMode controls when the bot responds: "mentions" (only @mentions) or "all" (all channel messages)
// Default: "all"
ListenMode string `toml:"listen_mode,omitempty"`
// IgnoreRepliesToOthers skips forwarding replies unless they reply to the bot itself.
// Default: false
IgnoreRepliesToOthers bool `toml:"ignore_replies_to_others,omitempty"`
}
DiscordSettings defines Discord bot configuration for the conductor bridge
type DisplaySettings ¶ added in v0.26.0
type DisplaySettings struct {
// FullRepaint forces a full screen clear on every render cycle instead of
// incremental redraws. Enable this if you see vertical drift or rendering
// artifacts in terminals that use unicode grapheme-cluster widths (e.g.
// Ghostty 1.3+ with grapheme-width-method=unicode).
// Can also be enabled via AGENTDECK_REPAINT=full env var.
// Default: false
FullRepaint bool `toml:"full_repaint,omitempty"`
// DefaultFilter sets the initial status filter when the TUI opens.
// Valid values: "" (all, default), "active" (hides error/stopped),
// "running", "waiting", "idle", "error".
// If set to "active" and no non-error sessions exist, falls back to showing all.
DefaultFilter string `toml:"default_filter,omitempty"`
// ActiveFilterLabel sets the label shown on the filter pill when the active
// filter is engaged. Default: "Open". Examples: "Active", "Live", "Open".
ActiveFilterLabel string `toml:"active_filter_label,omitempty"`
// ActiveFilterExcludes is the list of session statuses that the % "Open"
// filter hides. Default: ["error", "stopped"] — matches the original
// upstream behavior. Set to ["error"] to keep stopped/closed sessions
// visible while still hiding errors, or extend with "idle" for an
// aggressive "show only running/waiting" definition. Unknown statuses
// are dropped silently; if all entries are unknown the default applies.
// Valid statuses: "running", "waiting", "idle", "error", "starting",
// "stopped".
ActiveFilterExcludes []string `toml:"active_filter_excludes,omitempty"`
// IncludeCwdPrefix controls whether the terminal/pane title is prefixed
// with "[<cwd-basename>]" (e.g. "[my-project] feature work"). Default true
// preserves the historical format; set false to show only the session
// title. Consumed by the tmux set-titles-string builder.
IncludeCwdPrefix *bool `toml:"include_cwd_prefix,omitempty"`
// ShowSessionTimestamps appends a dim "Nm ago" badge to every session row.
// Default: false — opt-in to avoid crowding existing badges. See
// renderSessionItem for the timestamp source.
ShowSessionTimestamps bool `toml:"show_session_timestamps,omitempty"`
// ShowPaneTitles shows the dim tmux pane-title (task description) suffix on
// every session row, not just the selected one. Default: false — opt-in to
// avoid crowding narrow sidebars. See renderSessionItem for the source.
ShowPaneTitles bool `toml:"show_pane_titles,omitempty"`
}
DisplaySettings controls TUI rendering behavior.
func (DisplaySettings) GetActiveFilterExcludes ¶ added in v1.8.1
func (d DisplaySettings) GetActiveFilterExcludes() map[Status]bool
GetActiveFilterExcludes returns the resolved set of statuses the % filter should hide. Default {error, stopped} matches the original upstream hardcoded behavior; opt into ["error"] to keep stopped sessions visible. Unknown values are dropped; an empty resolved set falls back to the default.
func (DisplaySettings) GetDefaultFilter ¶ added in v1.3.3
func (d DisplaySettings) GetDefaultFilter() string
GetDefaultFilter returns the validated default_filter value, falling back to "" on invalid input.
func (DisplaySettings) GetFullRepaint ¶ added in v0.26.0
func (d DisplaySettings) GetFullRepaint() bool
GetFullRepaint returns whether full-repaint mode is active, checking the env var AGENTDECK_REPAINT=full as an override.
func (DisplaySettings) GetIncludeCwdPrefix ¶ added in v1.9.46
func (d DisplaySettings) GetIncludeCwdPrefix() bool
GetIncludeCwdPrefix reports whether the "[<cwd-basename>]" title prefix is shown. Defaults to true to preserve the historical title format.
type DockerSettings ¶ added in v0.19.17
type DockerSettings struct {
// DefaultImage is the sandbox image to use when not specified per-session.
DefaultImage string `toml:"default_image,omitempty"`
// DefaultEnabled enables sandbox by default for new sessions.
DefaultEnabled bool `toml:"default_enabled,omitempty"`
// CPULimit is the default CPU limit for sandboxed containers (e.g. "2.0").
CPULimit string `toml:"cpu_limit,omitempty"`
// MemoryLimit is the default memory limit for sandboxed containers (e.g. "4g").
MemoryLimit string `toml:"memory_limit,omitempty"`
// VolumeIgnores is a list of directories to exclude from the project mount.
VolumeIgnores []string `toml:"volume_ignores,omitempty"`
// Environment lists host environment variable names whose values are forwarded to the
// container at runtime via docker exec -e. The actual values are read from the host
// on each command invocation, so changes take effect without recreating the container.
Environment []string `toml:"environment,omitempty"`
// ExtraVolumes maps host paths to container paths for additional bind mounts.
ExtraVolumes map[string]string `toml:"extra_volumes,omitempty"`
// EnvironmentValues are static key=value pairs baked into the container at creation
// time via docker create -e. Unlike Environment (which forwards by name at runtime),
// these are fixed when the container is created.
EnvironmentValues map[string]string `toml:"environment_values,omitempty"`
// MountSSH mounts ~/.ssh read-only inside the container.
MountSSH bool `toml:"mount_ssh,omitempty"`
// AutoCleanup removes sandbox containers on session kill (default: true).
AutoCleanup *bool `toml:"auto_cleanup,omitempty"`
}
DockerSettings defines Docker sandbox configuration.
func GetDockerSettings ¶ added in v0.19.17
func GetDockerSettings() DockerSettings
GetDockerSettings returns docker sandbox settings with defaults applied.
func (DockerSettings) GetAutoCleanup ¶ added in v0.19.17
func (d DockerSettings) GetAutoCleanup() bool
GetAutoCleanup returns whether to auto-remove sandbox containers, defaulting to true.
type DoneSignal ¶ added in v1.9.36
type DoneSignal struct {
Status string // "ok" or "fail"
Summary string // free text to end of line; may be empty
}
DoneSignal is the parsed payload of a completion sentinel.
func ParseDoneSentinel ¶ added in v1.9.36
func ParseDoneSentinel(line string) (DoneSignal, bool)
ParseDoneSentinel parses a single line. It returns the signal and true only when the line contains the marker followed by a valid status=<ok|fail>. A summary= field is optional; its value runs to the end of the line. Anything that doesn't match — no marker, missing status, an unrecognized status value — is rejected (ok=false) rather than guessed at.
func ScanDoneSentinel ¶ added in v1.9.36
func ScanDoneSentinel(text string) (DoneSignal, bool)
ScanDoneSentinel scans multi-line text and returns the LAST line that parses as a valid sentinel. Last-wins so a worker that retried (printing fail then ok) reports its final outcome.
func ScanTranscriptTailForDone ¶ added in v1.9.58
func ScanTranscriptTailForDone(path string) (sig DoneSignal, found bool, pending bool)
ScanTranscriptTailForDone scans the transcript tail for a completion sentinel in the just-stopped MAIN-CHAIN assistant turn. The sentinel-bearing assistant record is NOT reliably the literal last line: Claude Code appends system / attachment records after the assistant turn, and sidechain (subagent) records interleave freely. Walk backwards over a bounded tail window, skipping sidechain traffic and non-turn records, until the first main-chain assistant or user record:
- assistant: this IS the turn that just stopped — scan it for the sentinel. pending=false.
- user: the just-stopped turn's reply has not been appended yet (the Stop hook outran the transcript flush). Scanning past it would re-read the PREVIOUS turn's sentinel — the observed deterministic one-turn lag — so report pending=true and let the caller retry once the record lands.
- window exhausted: nothing conclusive in the tail; also pending=true (the flushed record, once appended, lands at the very end and the next scan sees it immediately).
A missing/unreadable file yields pending=false so callers never spin on a path that will not resolve.
type ExperimentsSettings ¶ added in v0.8.43
type ExperimentsSettings struct {
// Directory is the base directory for experiments
// Default: ~/src/tries
Directory string `toml:"directory,omitempty"`
// DatePrefix adds YYYY-MM-DD- prefix to new experiment folders
// Default: true (nil = true)
DatePrefix *bool `toml:"date_prefix,omitempty"`
// DefaultTool is the AI tool to use for experiment sessions
// Default: "claude"
DefaultTool string `toml:"default_tool,omitempty"`
}
ExperimentsSettings defines experiment folder configuration
func GetExperimentsSettings ¶ added in v0.8.43
func GetExperimentsSettings() ExperimentsSettings
GetExperimentsSettings returns experiments settings with defaults applied
func (ExperimentsSettings) GetDatePrefix ¶ added in v1.9.61
func (e ExperimentsSettings) GetDatePrefix() bool
GetDatePrefix returns whether date prefixing is enabled (default: true).
type FeedbackSettings ¶ added in v1.7.38
type FeedbackSettings struct {
// Disabled suppresses all passive feedback prompts when true.
// Defaults to false. Set by RecordOptOut paths; cleared on re-enable.
Disabled bool `toml:"disabled,omitempty"`
}
FeedbackSettings controls the in-product feedback prompts. When Disabled is true, neither the auto-prompt (TUI) nor the post-launch auto-trigger (CLI, if any) will fire. Explicit `agent-deck feedback` invocations still run but show a re-enable prompt first. v1.7.38+.
type FieldRestartPolicy ¶ added in v1.7.70
type FieldRestartPolicy int
const ( FieldLive FieldRestartPolicy = iota FieldRestartRequired )
func RestartPolicyFor ¶ added in v1.7.70
func RestartPolicyFor(field string) FieldRestartPolicy
type FileTracker ¶ added in v0.5.3
FileTracker tracks file state for incremental updates
type FlickerDetector ¶ added in v1.9.0
type FlickerDetector struct {
// contains filtered or unexported fields
}
FlickerDetector observes status transitions per session and emits a synthetic "flicker_detected" WARN log when a session oscillates more than flickerThreshold times within flickerWindow.
Motivation: today's six-flicker oscillation incident was logged at Debug only and provided no aggregate signal. Operators had to grep status_changed lines manually. A single WARN per burst per session is enough to trigger an alert without spamming logs during a sustained flicker storm (rate-limited via flickerCooldown).
func GlobalFlickerDetector ¶ added in v1.9.0
func GlobalFlickerDetector() *FlickerDetector
GlobalFlickerDetector returns the process-wide detector used by the TUI's status_changed call site.
func NewFlickerDetector ¶ added in v1.9.0
func NewFlickerDetector() *FlickerDetector
NewFlickerDetector creates a fresh detector. Tests construct their own; production uses GlobalFlickerDetector.
func (*FlickerDetector) IsFlickering ¶ added in v1.9.67
func (d *FlickerDetector) IsFlickering(sessionID string) bool
IsFlickering reports whether a session currently has more than flickerThreshold transitions within the sliding window — i.e. it is flapping. Read-only (it prunes against the wall clock but records no new transition). Used by self-heal to treat a flapping session as quarantine-equivalent: a flicker is by definition not safely healable by restart (SELF-HEAL-DESIGN.md §3.4).
func (*FlickerDetector) Observe ¶ added in v1.9.0
func (d *FlickerDetector) Observe(sessionID, status string)
Observe records a status transition for sessionID at the current time. Callers should only invoke this when the new status differs from the previous status — Observe does not de-duplicate.
type FocusRequest ¶ added in v1.10.9
type FocusRequest struct {
ID string `json:"id"`
TS int64 `json:"ts"` // unix nanoseconds when the request was written
// Attach asks the TUI to open/attach the session (as if the user pressed
// Enter), not merely move the cursor to it. omitempty keeps a plain
// select-only request byte-identical to the pre-attach format.
Attach bool `json:"attach,omitempty"`
}
FocusRequest is the JSON payload stored under FocusRequestKey.
type ForkSettings ¶ added in v1.9.50
type ForkSettings struct {
// InheritFromParent, when true, makes the fork mirror the parent session and
// ignores the structural keys below. See Resolve.
InheritFromParent bool `toml:"inherit_from_parent,omitempty"`
// Worktree creates a new worktree + branch. nil => true.
Worktree *bool `toml:"worktree,omitempty"`
// WithState carries the parent's tracked uncommitted changes. nil => true.
WithState *bool `toml:"with_state,omitempty"`
// WithIgnored also copies gitignored files (implies WithState). nil => false:
// the gitignored tree is unbounded (data sets, virtual envs, node_modules)
// and may carry secrets (.env), so copying it is opt-in. See GetWithIgnored.
WithIgnored *bool `toml:"with_ignored,omitempty"`
// Docker selects sandbox behavior: "auto" (match parent) | "on" | "off".
// nil/unknown => "auto". Mirrors the [tmux].launch_as string-enum convention.
Docker *string `toml:"docker,omitempty"`
// BranchPrefix is the auto branch-name prefix. "" => "fork/".
BranchPrefix string `toml:"branch_prefix,omitempty"`
}
ForkSettings controls quick-fork (f) and fork-dialog (Shift+F) defaults. Unset structural toggles default to the comprehensive built-in (ON); these defaults are independent of [worktree]/docker default_enabled, which govern non-fork session creation. *bool is required so "absent" reads as ON.
func (ForkSettings) GetBranchPrefix ¶ added in v1.9.50
func (f ForkSettings) GetBranchPrefix() string
GetBranchPrefix returns the auto branch-name prefix (default "fork/").
func (ForkSettings) GetDocker ¶ added in v1.9.50
func (f ForkSettings) GetDocker() string
GetDocker returns the canonical docker mode: "auto" | "on" | "off". Mirrors GetLaunchAs: lowercase/trim, unknown/nil -> "auto".
func (ForkSettings) GetWithIgnored ¶ added in v1.9.50
func (f ForkSettings) GetWithIgnored() bool
GetWithIgnored reports whether forks copy gitignored files (default OFF). Off by default because the gitignored tree is unbounded (data sets, virtual envs, node_modules) and can carry secrets (.env); copying it silently blocks the fork with no size cap or progress. Opt in per fork via the Shift+F dialog, globally via [fork].with_ignored = true, or wholesale via inherit_from_parent.
func (ForkSettings) GetWithState ¶ added in v1.9.50
func (f ForkSettings) GetWithState() bool
GetWithState reports whether forks carry tracked state (default ON).
func (ForkSettings) GetWorktree ¶ added in v1.9.50
func (f ForkSettings) GetWorktree() bool
GetWorktree reports whether forks create a worktree (default ON).
func (ForkSettings) Resolve ¶ added in v1.9.50
func (f ForkSettings) Resolve(parentSandboxed bool) ResolvedForkPlan
Resolve turns ForkSettings + the parent's Docker state into a concrete plan. parentSandboxed is source.IsSandboxed(). When InheritFromParent is set, the fork mirrors the parent: worktree+state+gitignored ON (the parent is a real working tree) and Sandbox matches the parent, ignoring the structural keys.
type GeminiMCPConfig ¶ added in v0.8.1
type GeminiMCPConfig struct {
MCPServers map[string]MCPServerConfig `json:"mcpServers"`
}
GeminiMCPConfig represents settings.json structure VERIFIED: Actual settings.json does NOT have mcp.allowed/excluded (Simplified structure compared to research docs)
type GeminiModelPricing ¶ added in v0.8.35
GeminiModelPricing holds pricing per million tokens
type GeminiSessionAnalytics ¶ added in v0.8.35
type GeminiSessionAnalytics struct {
// Token usage
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
// Current context size (last turn's input + cache read tokens)
CurrentContextTokens int `json:"current_context_tokens"`
// Session metrics
TotalTurns int `json:"total_turns"`
Duration time.Duration `json:"duration"`
StartTime time.Time `json:"start_time"`
LastActive time.Time `json:"last_active"`
// Cost estimation
EstimatedCost float64 `json:"estimated_cost"`
// Model detected from session file messages
Model string `json:"model,omitempty"`
// In-memory cache: last file modification time (skip re-parse if unchanged)
LastFileModTime time.Time `json:"-"`
}
GeminiSessionAnalytics holds metrics for a Gemini session
func (*GeminiSessionAnalytics) CalculateCost ¶ added in v0.8.35
func (a *GeminiSessionAnalytics) CalculateCost(model string) float64
CalculateCost estimates session cost based on token usage and model pricing
func (*GeminiSessionAnalytics) TotalTokens ¶ added in v0.8.35
func (a *GeminiSessionAnalytics) TotalTokens() int
TotalTokens returns the sum of input and output tokens
type GeminiSessionInfo ¶ added in v0.8.1
type GeminiSessionInfo struct {
SessionID string // Full UUID
Filename string // session-2025-12-26T15-09-4d8fcb4d.json
StartTime time.Time
LastUpdated time.Time
MessageCount int
}
GeminiSessionInfo holds parsed session metadata
func ListGeminiSessions ¶ added in v0.8.1
func ListGeminiSessions(projectPath string) ([]GeminiSessionInfo, error)
ListGeminiSessions returns all sessions for a project path Scans ~/.gemini/tmp/<hash>/chats/ and parses session files Sorted by LastUpdated (most recent first)
type GeminiSettings ¶ added in v0.8.35
type GeminiSettings struct {
// YoloMode enables --yolo flag for Gemini sessions (auto-approve all actions)
// Default: false
YoloMode bool `toml:"yolo_mode,omitempty"`
// DefaultModel is the model to use for new Gemini sessions (e.g., "gemini-2.5-flash")
// If empty, Gemini CLI uses its own default
DefaultModel string `toml:"default_model,omitempty"`
// EnvFile is a .env file specific to Gemini sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file,omitempty"`
// Command overrides the default binary/invocation for Gemini sessions.
// Supports flags (e.g., "gemini --custom-flag"). Default: "gemini"
Command string `toml:"command,omitempty"`
}
GeminiSettings defines Gemini CLI configuration
type GlobalSearchIndex ¶ added in v0.5.3
type GlobalSearchIndex struct {
// contains filtered or unexported fields
}
GlobalSearchIndex manages the searchable session index
func NewGlobalSearchIndex ¶ added in v0.5.3
func NewGlobalSearchIndex(claudeDir string, config GlobalSearchSettings) (*GlobalSearchIndex, error)
NewGlobalSearchIndex creates a new search index
func (*GlobalSearchIndex) Close ¶ added in v0.5.3
func (idx *GlobalSearchIndex) Close()
Close shuts down the index and releases all memory
func (*GlobalSearchIndex) EntryCount ¶ added in v0.5.3
func (idx *GlobalSearchIndex) EntryCount() int
EntryCount returns the number of indexed entries
func (*GlobalSearchIndex) FuzzySearch ¶ added in v0.5.3
func (idx *GlobalSearchIndex) FuzzySearch(query string) []*SearchResult
FuzzySearch performs fuzzy matching with typo tolerance
func (*GlobalSearchIndex) GetTier ¶ added in v0.5.3
func (idx *GlobalSearchIndex) GetTier() SearchTier
GetTier returns the current search tier
func (*GlobalSearchIndex) IsLoading ¶ added in v0.5.3
func (idx *GlobalSearchIndex) IsLoading() bool
IsLoading returns true if the index is still loading
func (*GlobalSearchIndex) Search ¶ added in v0.5.3
func (idx *GlobalSearchIndex) Search(query string) []*SearchResult
Search performs a simple substring search
type GlobalSearchSettings ¶ added in v0.5.3
type GlobalSearchSettings struct {
// Enabled enables/disables global search feature (default: true)
Enabled *bool `toml:"enabled,omitempty"`
// Tier controls search strategy: "auto", "instant", "balanced", "disabled"
// auto: Auto-detect based on data size (recommended)
// instant: Force full in-memory (fast, uses more RAM)
// balanced: Force LRU cache mode (slower, capped RAM)
// disabled: Disable global search entirely
Tier string `toml:"tier,omitempty"`
// MemoryLimitMB caps memory usage for search index (default: 100)
// Only applies to balanced tier
MemoryLimitMB int `toml:"memory_limit_mb,omitzero"`
// RecentDays limits search to sessions from last N days (0 = all)
// Reduces index size for users with long history (default: 90)
RecentDays int `toml:"recent_days,omitzero"`
// IndexRateLimit limits files indexed per second during background indexing
// Lower = less CPU impact (default: 20)
IndexRateLimit int `toml:"index_rate_limit,omitzero"`
}
GlobalSearchSettings defines global conversation search configuration
func (GlobalSearchSettings) GetEnabled ¶ added in v1.9.61
func (g GlobalSearchSettings) GetEnabled() bool
type Group ¶
type Group struct {
Name string
Path string // Full path like "projects" or "projects/devops"
Expanded bool
Sessions []*Instance
Order int
DefaultPath string // Explicit default path for new sessions in this group
// MaxConcurrent caps simultaneous running sessions in this group (v1.9.1).
// 0 = unlimited (legacy default for groups predating this field); 1 = serial
// (default for newly-created groups); N>=2 = bounded parallelism. Negative
// values are treated as unlimited (explicit opt-out).
MaxConcurrent int
}
Group represents a group of sessions
type GroupActivity ¶ added in v1.9.58
type GroupActivity struct {
HasAny bool // group (or a descendant) has at least one session
HasActive bool // group (or a descendant) has at least one active session
}
GroupActivity summarizes, for a single group path, whether it contains any sessions and whether any of them are active — computed over the full tree (direct + descendant sessions), independent of expand/collapse state. This is what lets PartitionByViewMode place a *collapsed* group's header correctly: the flattened list omits a collapsed group's session rows, so header placement cannot be derived from rows alone.
type GroupBudget ¶ added in v0.26.4
type GroupBudget struct {
DailyLimit float64 `toml:"daily_limit,omitzero"`
}
type GroupClaudeResolution ¶ added in v1.10.9
type GroupClaudeResolution struct {
ConfigDir string `json:"config_dir,omitempty"`
ConfigDirSource string `json:"config_dir_source"`
EnvFile string `json:"env_file,omitempty"`
EnvFileSource string `json:"env_file_source,omitempty"`
EnvFileResolved string `json:"env_file_resolved,omitempty"`
// EnvFileExists is meaningful only when EnvFileResolved is absolute;
// a relative env_file resolves against each session's working dir.
EnvFileExists bool `json:"env_file_exists"`
Command string `json:"command"`
CommandSource string `json:"command_source"`
Model string `json:"model,omitempty"`
ModelSource string `json:"model_source,omitempty"`
Env map[string]string `json:"env,omitempty"`
Skills []string `json:"skills,omitempty"`
Plugins []string `json:"plugins,omitempty"`
MCPs []string `json:"mcps,omitempty"`
// ConfigError carries the config.toml load error verbatim when the
// file failed to parse — in that state every value above is a default
// and the stanza the user is debugging is NOT in effect.
ConfigError string `json:"config_error,omitempty"`
}
GroupClaudeResolution is the resolved view of the effective Claude configuration for a group path — what a session created in that group would actually launch with. Built for `agent-deck group show --resolved` so a misconfigured stanza is diagnosable: a typo'd group key, a TOML parse error, or a missing env_file are otherwise indistinguishable at launch (the spawn silently proceeds on defaults).
Source labels: "group:<path>" (the ancestor that matched), "global", "env", "profile", "default", or "" when unset.
func ResolveGroupClaude ¶ added in v1.10.9
func ResolveGroupClaude(groupPath string) GroupClaudeResolution
ResolveGroupClaude resolves the effective Claude settings for a group path using the same chains the spawn builders use (group ancestor-walk → global → default; env beats group for config_dir on the no-instance chain). Conductor-level overrides are per-session and therefore not part of a group view.
type GroupClaudeSettings ¶ added in v1.5.4
type GroupClaudeSettings struct {
// ConfigDir overrides [claude].config_dir for sessions in this group.
ConfigDir string `toml:"config_dir,omitempty"`
// EnvFile overrides [claude].env_file for sessions in this group.
EnvFile string `toml:"env_file,omitempty"`
// Command overrides [claude].command for sessions in this group
// (e.g. a wrapper like "claude-vertex"). Same parity Hermes already
// has via GroupHermesSettings.Command. Resolution:
// conductor > group (ancestor-walking) > global [claude].command > "claude".
Command string `toml:"command,omitempty"`
// Model is the model default for sessions in this group (e.g.
// "claude-sonnet-4-6" or an alias like "sonnet"). An explicit
// per-session model (CLI --model, new-session dialog) wins; empty
// falls through (#1172 semantics).
Model string `toml:"model,omitempty"`
// Env is an inline env map exported in the spawn command AFTER the
// env_file source, so an inline key deterministically wins over the
// same key from the file. Precedent: [tools.X].env.
Env map[string]string `toml:"env,omitempty"`
// Skills lists declarative skill-loadout entries ("<source>/<name>")
// attached to sessions in this group at create and re-asserted on
// every start (ApplyConfiguredLoadout — attach-only floor semantics).
Skills []string `toml:"skills,omitempty"`
// Plugins lists [plugins.X] catalog keys unioned into Instance.Plugins.
// Catalog resolution remains the single plugin enablement path.
Plugins []string `toml:"plugins,omitempty"`
// MCPs lists [mcps.X] catalog names appended to the local .mcp.json
// of sessions in this group. Same floor semantics as Skills.
MCPs []string `toml:"mcps,omitempty"`
}
GroupClaudeSettings defines group-specific Claude overrides.
The key surface deliberately mirrors ConductorClaudeSettings (CFG-08 established the two blocks as mirrors); keep them in sync when adding keys. New keys use omitempty so SaveUserConfig does not emit zero-value fields into every group stanza (see issue #1360).
type GroupData ¶
type GroupData struct {
Name string `json:"name"`
Path string `json:"path"`
Expanded bool `json:"expanded"`
Order int `json:"order"`
DefaultPath string `json:"default_path,omitempty"`
// MaxConcurrent caps simultaneous running sessions in this group (v1.9.1).
// 0 = unlimited (legacy default for groups predating this field); 1 = serial
// (default for newly-created groups); N>=2 = bounded parallelism.
MaxConcurrent int `json:"max_concurrent,omitempty"`
}
GroupData represents serializable group data
type GroupDefaultsSettings ¶ added in v1.10.9
type GroupDefaultsSettings struct {
// MaxConcurrent is the max_concurrent value assigned to new groups created
// via `group create`, the TUI dialog, the web API, and the launch/session
// auto-create paths. Pointer to distinguish:
// nil → unset → built-in serial default (1) [byte-for-byte v1.9.1]
// *0 → new groups are unlimited
// *N (N>0) → new groups capped at N
// An explicit `group create --max-concurrent` flag overrides this.
MaxConcurrent *int `toml:"max_concurrent,omitempty"`
}
GroupDefaultsSettings carries [group_defaults] — defaults stamped onto new groups at creation time. Distinct from per-group [groups."<path>"] overrides.
type GroupHermesSettings ¶ added in v1.9.47
type GroupHermesSettings struct {
Command string `toml:"command,omitempty"`
EnvFile string `toml:"env_file,omitempty"`
YoloMode bool `toml:"yolo_mode,omitempty"`
GatewayURL string `toml:"gateway_url,omitempty"`
DashboardURL string `toml:"dashboard_url,omitempty"`
APITokenEnv string `toml:"api_token_env,omitempty"`
}
GroupHermesSettings defines group-specific Hermes overrides.
type GroupSettings ¶ added in v1.5.4
type GroupSettings struct {
// Create ensures the group exists on startup.
Create bool `toml:"create,omitempty"`
// DefaultPath sets the default working directory for new sessions in this group.
DefaultPath string `toml:"default_path,omitempty"`
// Claude defines Claude Code overrides for a specific group.
Claude GroupClaudeSettings `toml:"claude,omitempty"`
// Hermes defines Hermes overrides for a specific group.
Hermes GroupHermesSettings `toml:"hermes,omitempty"`
}
GroupSettings defines per-group configuration overrides.
type GroupTree ¶
type GroupTree struct {
Groups map[string]*Group // path -> group
GroupList []*Group // Ordered list of groups
Expanded map[string]bool // Collapsed state persistence
// DefaultMaxConcurrent is the max_concurrent value copied into groups
// created via CreateGroup/CreateSubgroup. nil → built-in serial default (1),
// preserving v1.9.1 behavior when [group_defaults] is unset. Seeded by the
// command/UI layer from [group_defaults].max_concurrent before a create; an
// explicit `group create --max-concurrent` flag still wins per-group.
DefaultMaxConcurrent *int
}
GroupTree manages hierarchical session organization
func NewGroupTree ¶
NewGroupTree creates a new group tree from instances
func NewGroupTreeWithGroups ¶
NewGroupTreeWithGroups creates a group tree from instances and stored group data
func (*GroupTree) AddSession ¶
AddSession adds a session to the appropriate group
func (*GroupTree) CollapseGroup ¶
CollapseGroup collapses a group
func (*GroupTree) CreateGroup ¶
CreateGroup creates a new empty group
func (*GroupTree) CreateGroupPath ¶ added in v1.9.55
CreateGroupPath ensures every level of a (possibly nested) group path exists, creating any missing intermediate groups, and returns the leaf group.
Unlike CreateGroup, it treats "/" as a path separator instead of letting sanitizeGroupName flatten it into a hyphen, so "work/bar" creates "work" and "work/bar" rather than a single flat "work-bar" group (see issue #1357).
func (*GroupTree) CreateSubgroup ¶
CreateSubgroup creates a new empty group under a parent group
func (*GroupTree) DefaultPathForGroup ¶ added in v0.19.1
DefaultPathForGroup returns the effective default path for creating new sessions in the group: explicit configured default_path first, then most recent session path.
func (*GroupTree) DeleteGroup ¶
DeleteGroup deletes a group, all its subgroups, and moves all sessions to default
func (*GroupTree) DemoteSession ¶ added in v1.9.2
DemoteSession converts a top-level session into a sub-session of the previous top-level peer in the same group, inserting it as that peer's last child. No-op if there is no previous peer (group's first top-level), if the session is already a sub-session, or if it has its own children — single-level nesting only, mirroring the validation in `session set-parent`.
func (*GroupTree) ExpandGroup ¶
ExpandGroup expands a group
func (*GroupTree) ExpandGroupWithParents ¶
ExpandGroupWithParents expands a group and all its parent groups This ensures the group and its contents are visible in the flattened view
func (*GroupTree) GetAllInstances ¶
GetAllInstances returns all instances in order
func (*GroupTree) GetGroupNames ¶
GetGroupNames returns all group names for selection
func (*GroupTree) GetGroupPaths ¶
GetGroupPaths returns all group paths for selection
func (*GroupTree) GroupActivityMap ¶ added in v1.9.58
func (t *GroupTree) GroupActivityMap(viewArchived bool) map[string]GroupActivity
GroupActivityMap returns per-group-path activity aggregated over the sessions in the tree (collapse-agnostic), propagated to ancestor paths. Only sessions matching the current archive view are counted: with viewArchived=false (the normal active view) archived sessions are ignored, so a group whose sessions are ALL archived reports HasAny=false and is treated as empty for view-mode placement — it sinks below the divider with the other empty groups rather than masquerading as a collapsed-but-populated group on top.
func (*GroupTree) GroupCount ¶
GroupCount returns total group count
func (*GroupTree) MoveGroupDown ¶
MoveGroupDown moves a group down in the order (only within siblings at same level)
func (*GroupTree) MoveGroupTo ¶ added in v1.7.29
MoveGroupTo reparents a group (and its entire subtree) under destParentPath. An empty destParentPath promotes the group to root level. Returns an error for: unknown source, source == DefaultGroupPath, unknown destParent, destParent == source or its descendant (circular), or a collision at the target path. Same-parent call is a no-op.
This is the engine behind the #447 "group change" CLI / TUI.
func (*GroupTree) MoveGroupUp ¶
MoveGroupUp moves a group up in the order (only within siblings at same level)
func (*GroupTree) MoveSessionDown ¶
MoveSessionDown moves a session down among its visual siblings. See MoveSessionUp for the sibling-aware semantics; this is the symmetric case that swaps with the next same-parent session in the slice.
When a sub-session has no following same-parent sibling (it is at the bottom of its parent's children block), it is promoted to top-level at its current slice position so the renderer shows it as a top-level peer immediately after the parent's block. At the group's bottom boundary (no following top-level peer) the call is a no-op.
func (*GroupTree) MoveSessionToGroup ¶
MoveSessionToGroup moves a session to a different group
func (*GroupTree) MoveSessionUp ¶
MoveSessionUp moves a session up among its visual siblings: top-level sessions (empty ParentSessionID) reorder among other top-level sessions in the same group; sub-sessions reorder among other sub-sessions of the same parent. Non-siblings interleaved in the flat slice are skipped.
When a sub-session has no previous same-parent sibling (it is at the top of its parent's children block), it is promoted to top-level and inserted in the slice immediately before the parent. At the group's top boundary (no previous top-level peer) the call is a no-op; cross-group moves remain on the M shortcut.
func (*GroupTree) PromoteSession ¶ added in v1.9.2
PromoteSession converts a sub-session into a top-level peer in the same group. Slice position is preserved so the renderer places the session as a top-level peer immediately after its former parent's children block. Top-level sessions are unchanged.
func (*GroupTree) RemoveSession ¶
RemoveSession removes a session from its group
func (*GroupTree) RenameGroup ¶
RenameGroup renames a group and updates all subgroups. Returns ErrGroupNotFound if oldPath doesn't exist, or ErrGroupAlreadyExists if the target path collides.
func (*GroupTree) RenameTargetPath ¶ added in v1.10.9
RenameTargetPath returns the group path that RenameGroup(oldPath, newName) would move the group to, applying the same sanitization and parent-path preservation. Exposed so callers can detect a collision with an existing, different group at the target before renaming (see the reload-race reapply).
func (*GroupTree) SessionCount ¶
SessionCount returns total session count
func (*GroupTree) SessionCountForGroup ¶ added in v0.8.37
SessionCountForGroup returns session count for a group INCLUDING all its subgroups This enables hierarchical counts like "Project (5)" where 5 includes all nested sessions
func (*GroupTree) SetDefaultPathForGroup ¶ added in v0.19.1
SetDefaultPathForGroup sets (or clears) an explicit default path for a group.
func (*GroupTree) ShallowCopyForSave ¶ added in v0.7.0
ShallowCopyForSave creates a copy of the GroupTree that's safe to use from a goroutine for saving purposes. It deep copies the Group structs to prevent data races when the main thread modifies group fields (Name, Path, Expanded, Order) while a background goroutine reads them.
func (*GroupTree) SyncWithInstances ¶
SyncWithInstances updates the tree with a new set of instances while preserving existing group structure (including empty groups)
func (*GroupTree) ToggleGroup ¶
ToggleGroup toggles the expanded state of a group
type GroupViewMode ¶ added in v1.9.58
type GroupViewMode int
GroupViewMode controls how the flattened session list is partitioned into a "top" section and a "bottom" section, separated by a divider. It is toggled from the TUI (cycled by a hotkey) and persisted across restarts.
const ( // GroupViewNormal renders the list as-is (no partitioning). GroupViewNormal GroupViewMode = iota // GroupViewActiveTop hoists groups/subgroups that contain an active // (running/waiting/starting) session to the top, showing only their active // sessions there; the same group re-appears below the divider with its // remaining sessions. GroupViewActiveTop // GroupViewPopulatedTop hoists groups that contain any session to the top // (with all their sessions, unsplit) and sinks empty groups below the // divider. GroupViewPopulatedTop )
func (GroupViewMode) Label ¶ added in v1.9.58
func (m GroupViewMode) Label() string
Label returns a short human-readable name for the mode (for status hints).
type HTTPServerConfig ¶ added in v0.8.96
type HTTPServerConfig struct {
// Command is the executable to run (e.g., "uvx", "python", "node")
Command string `toml:"command,omitempty"`
// Args are command-line arguments for the server
Args []string `toml:"args,omitempty"`
// Env is environment variables for the server process
Env map[string]string `toml:"env,omitempty"`
// StartupTimeout is milliseconds to wait for server to become ready (default: 5000)
StartupTimeout int `toml:"startup_timeout,omitzero"`
// HealthCheck is an optional health endpoint URL to poll (e.g., "http://localhost:30000/health")
// If not set, the main URL is used for health checking
HealthCheck string `toml:"health_check,omitempty"`
}
HTTPServerConfig defines how to auto-start an HTTP MCP server
func (*HTTPServerConfig) GetStartupTimeout ¶ added in v0.8.96
func (c *HTTPServerConfig) GetStartupTimeout() int
GetStartupTimeout returns the startup timeout in milliseconds, defaulting to 5000ms
type HermesOptions ¶ added in v1.9.19
type HermesOptions struct {
// YoloMode enables --yolo flag (auto-approve all tool calls).
// nil = inherit from config, true/false = explicit override.
YoloMode *bool `json:"yolo_mode,omitempty"`
}
HermesOptions holds launch options for Hermes Agent CLI sessions. Binary: `hermes` from github.com/NousResearch/hermes-agent (MIT, v0.13.0+). Status detection: process-alive/dead only (content-sniffing deferred). NOTE: CLI --yolo override (via applyCLIYoloOverride) is deferred until HermesOptions is wired into the launch command builder.
func NewHermesOptions ¶ added in v1.9.19
func NewHermesOptions(config *UserConfig) *HermesOptions
NewHermesOptions creates HermesOptions with defaults from config.
func UnmarshalHermesOptions ¶ added in v1.9.19
func UnmarshalHermesOptions(data json.RawMessage) (*HermesOptions, error)
UnmarshalHermesOptions deserializes HermesOptions from JSON wrapper.
func (*HermesOptions) ToArgs ¶ added in v1.9.19
func (o *HermesOptions) ToArgs() []string
ToArgs returns command-line arguments based on options.
func (*HermesOptions) ToolName ¶ added in v1.9.19
func (o *HermesOptions) ToolName() string
ToolName returns "hermes"
type HermesSettings ¶ added in v1.9.19
type HermesSettings struct {
// Command is the Hermes CLI command or invocation to use.
// Supports flags (e.g., "hermes --model gpt-5.5-pro --provider openai").
// Default: "hermes"
Command string `toml:"command,omitempty"`
// EnvFile is a .env file specific to Hermes sessions (sourced before
// the `hermes` command runs). Optional.
EnvFile string `toml:"env_file,omitempty"`
// YoloMode enables --yolo flag for Hermes sessions (auto-approve all tool calls).
// Default: false
YoloMode bool `toml:"yolo_mode,omitempty"`
// GatewayURL is the WebSocket URL of the Hermes gateway for health checks.
// Default: "" (no gateway health check)
GatewayURL string `toml:"gateway_url,omitempty"`
// DashboardURL is the Hermes dashboard API endpoint.
// Default: "" (dashboard integration disabled)
DashboardURL string `toml:"dashboard_url,omitempty"`
// APITokenEnv is the environment variable name containing the Hermes API token.
// Default: "" (uses HERMES_API_TOKEN if set)
APITokenEnv string `toml:"api_token_env,omitempty"`
// WorkspaceDir is the base directory for Hermes shared workspace sessions.
// Default: "" (uses os.TempDir()/hermes-workspaces)
WorkspaceDir string `toml:"workspace_dir,omitempty"`
}
HermesSettings defines Hermes Agent CLI configuration. Binary: `hermes` from github.com/NousResearch/hermes-agent (MIT, v0.13.0+). Status detection: process-alive/dead only (content-sniffing deferred).
type HookStatus ¶ added in v0.16.0
type HookStatus struct {
Status string // running, idle, waiting, dead
SessionID string // Claude session ID
Event string // Hook event name
UpdatedAt time.Time // When this status was received
// DoneStatus/DoneSummary carry a worker-printed completion sentinel
// detected on the Stop edge (issue #1186). Empty for ordinary turns.
DoneStatus string // "ok" or "fail" when a completion sentinel was seen
DoneSummary string // free-text completion summary
// TranscriptPath is set when the Stop-edge sentinel scan was inconclusive
// because the turn's assistant record had not flushed yet (issue #1186
// flush race). The transition daemon re-scans this path on its poll loop.
TranscriptPath string
}
HookStatus holds the decoded status from a hook status file.
type IdleTimeoutWatcher ¶ added in v1.9.29
type IdleTimeoutWatcher struct {
// contains filtered or unexported fields
}
IdleTimeoutWatcher polls running sessions, tracks per-instance pane-content hashes, and triggers Stop when content stays unchanged longer than IdleTimeoutSecs.
Safe for use from one goroutine. Tick is the unit of work; production code drives Tick from an existing 2-second ticker (e.g. statusWorker in the TUI) and ticks roughly once per minute via Start.
func NewIdleTimeoutWatcher ¶ added in v1.9.29
func NewIdleTimeoutWatcher(cfg IdleTimeoutWatcherConfig) *IdleTimeoutWatcher
NewIdleTimeoutWatcher constructs a watcher with production defaults filled in for any nil config callback.
func (*IdleTimeoutWatcher) Start ¶ added in v1.9.29
func (w *IdleTimeoutWatcher) Start(ctx context.Context, interval time.Duration, snapshot func() []*Instance)
Start runs Tick at the given interval until ctx is cancelled. instances is re-fetched each tick via the snapshot func; pass the parent TUI/daemon's instance accessor so the watcher always sees fresh state.
func (*IdleTimeoutWatcher) Tick ¶ added in v1.9.29
func (w *IdleTimeoutWatcher) Tick(instances []*Instance)
Tick scans the given instances. Sessions with IdleTimeoutSecs <= 0 are skipped (and their tracking state is cleared so toggling off via SetField works on the next tick). Non-running sessions are skipped too.
type IdleTimeoutWatcherConfig ¶ added in v1.9.29
type IdleTimeoutWatcherConfig struct {
// Now is the clock source. Defaults to time.Now.
Now func() time.Time
// Capture returns the current tmux pane content for the instance.
// Defaults to instance.tmuxSession.CapturePane().
Capture func(*Instance) (string, error)
// Stop is invoked once when an instance has been idle for >= its
// IdleTimeoutSecs. Defaults to inst.Kill().
Stop func(*Instance) error
// LogEvent persists a "session lifecycle" row. Defaults to
// WriteSessionLifecycleEvent.
LogEvent func(SessionLifecycleEvent) error
}
IdleTimeoutWatcherConfig wires the watcher to its environment. All fields are optional and default to production behavior; tests inject Now / Capture / Stop so they don't touch real tmux or real clocks.
type Instance ¶
type Instance struct {
ID string `json:"id"`
Title string `json:"title"`
ProjectPath string `json:"project_path"`
GroupPath string `json:"group_path"` // e.g., "projects/devops"
Order int `json:"order"` // Position within group (for reorder persistence)
// Pin anchors this session to the top or bottom of its group, exempt from
// the status/recency sort (pin-sessions feature). PinNone is the default.
Pin PinMode `json:"pin,omitempty"`
ParentSessionID string `json:"parent_session_id,omitempty"` // Links to parent session (makes this a sub-session)
ParentProjectPath string `json:"parent_project_path,omitempty"` // Parent's project path (for --add-dir access)
IsConductor bool `json:"is_conductor,omitempty"` // True if this session is a conductor orchestrator
NoTransitionNotify bool `json:"no_transition_notify,omitempty"` // Suppress transition event dispatch for this session
// TitleLocked, when true, blocks Claude's session name from syncing into
// the agent-deck Title (issue #697). Conductors launch workers with a
// semantic title (e.g. "SCRUM-351") that Claude would otherwise overwrite
// with its auto-generated summary on the next hook event. Set via
// `--title-lock` on add/launch or `session set-title-lock`.
TitleLocked bool `json:"title_locked,omitempty"`
// AutoName, when true, marks Title as a machine-generated adjective-noun
// handle (from a --quick / TUI-Q create). The TUI then displays the
// session's live Claude task description (tmux pane title) in place of the
// handle. Any explicit rename clears this so the user-chosen name is shown
// verbatim. See docs/superpowers/specs/2026-06-01-quick-session-claude-name-design.md.
// Guarded by i.mu for runtime reads/writes; use GetAutoName/SetAutoName once
// an Instance is shared with background workers or the TUI render loop.
AutoName bool `json:"auto_name,omitempty"`
// Git worktree support
WorktreePath string `json:"worktree_path,omitempty"` // Path to worktree (if session is in worktree)
WorktreeRepoRoot string `json:"worktree_repo_root,omitempty"` // Original repo root
WorktreeBranch string `json:"worktree_branch,omitempty"` // Branch name in worktree
WorktreeType string `json:"worktree_type,omitempty"` // "git", "jujutsu", or "" (legacy = git)
// Account is the per-session named account slot (issue #924). Maps to
// `[profiles.<account>.claude].config_dir` in ~/.agent-deck/config.toml
// at spawn time and becomes the most-specific level in the
// CLAUDE_CONFIG_DIR resolution chain — beating conductor / group / env.
// Switching the value requires a session restart (the Option 1 MVP
// tradeoff): the in-flight Claude conversation is lost since the new
// account's settings.json and history live elsewhere. Empty means
// "fall through to conductor/group/env/profile/global/default" so
// pre-#924 sessions keep their existing behavior unchanged.
Account string `json:"account,omitempty"`
// Multi-repo support
MultiRepoEnabled bool `json:"multi_repo_enabled,omitempty"`
AdditionalPaths []string `json:"additional_paths,omitempty"` // Paths beyond ProjectPath
MultiRepoTempDir string `json:"multi_repo_temp_dir,omitempty"` // Temp cwd for multi-repo sessions
MultiRepoWorktrees []MultiRepoWorktree `json:"multi_repo_worktrees,omitempty"`
Command string `json:"command"`
Wrapper string `json:"wrapper,omitempty"` // Optional wrapper command with {command} placeholder
Tool string `json:"tool"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
LastAccessedAt time.Time `json:"last_accessed_at,omitempty"` // When user last attached
// ArchivedAt is set when the user archives the session (non-zero = archived).
ArchivedAt time.Time `json:"archived_at,omitempty"`
// LastStartedAt is the wall-clock time of the most recent successful
// Start() / StartWithMessage() / Restart() call. Persisted so short-lived
// CLI invocations can see it across processes (issue #30): a restart
// queued seconds after a start must detect the session is already fresh
// and skip the teardown. Zero value means "unknown" (old record or
// never started) and callers MUST NOT treat zero as "just now".
LastStartedAt time.Time `json:"last_started_at,omitempty"`
// Claude Code integration
ClaudeSessionID string `json:"claude_session_id,omitempty"`
ClaudeDetectedAt time.Time `json:"claude_detected_at,omitempty"`
// Gemini CLI integration
GeminiSessionID string `json:"gemini_session_id,omitempty"`
GeminiDetectedAt time.Time `json:"gemini_detected_at,omitempty"`
GeminiYoloMode *bool `json:"gemini_yolo_mode,omitempty"` // Per-session override (nil = use global config)
GeminiModel string `json:"gemini_model,omitempty"` // Active model for this session
GeminiAnalytics *GeminiSessionAnalytics `json:"gemini_analytics,omitempty"` // Per-session analytics
// OpenCode CLI integration
OpenCodeSessionID string `json:"opencode_session_id,omitempty"`
OpenCodeDetectedAt time.Time `json:"opencode_detected_at,omitempty"`
OpenCodeStartedAt int64 `json:"-"` // Unix millis when we started OpenCode (for session matching, not persisted)
OpenCodePort int `json:"opencode_port,omitempty"` // Localhost port of OpenCode's event server (issue #1614); 0 = none
// Codex CLI integration
CodexSessionID string `json:"codex_session_id,omitempty"`
CodexDetectedAt time.Time `json:"codex_detected_at,omitempty"`
CodexStartedAt int64 `json:"-"` // Unix millis when we started Codex (for session matching, not persisted)
// GitHub Copilot CLI integration
CopilotSessionID string `json:"copilot_session_id,omitempty"`
CopilotDetectedAt time.Time `json:"copilot_detected_at,omitempty"`
CopilotStartedAt int64 `json:"-"` // Unix millis when we started Copilot (for session matching, not persisted)
CopilotModel string `json:"copilot_model,omitempty"` // Active model for this session
CopilotAllowAll bool `json:"copilot_allow_all,omitempty"` // Per-session --allow-all override
// Latest user input for context (extracted from session files)
LatestPrompt string `json:"latest_prompt,omitempty"`
Notes string `json:"notes,omitempty"`
// Color is an optional user-chosen tint for this session's TUI row (issue #391).
// Accepts a lipgloss-compatible color spec:
// - "#RRGGBB" - truecolor hex
// - "0".."255" - ANSI 256-palette index as a decimal string
// - "" - default (no tint, current rendering unchanged)
// Validation happens at CLI/API boundary in cmd/agent-deck/session_cmd.go.
// Empty string is the default so the field is fully opt-in and never
// changes rendering for users who don't set it.
Color string `json:"color,omitempty"`
// Docker sandbox support.
Sandbox *SandboxConfig `json:"sandbox,omitempty"`
SandboxContainer string `json:"sandbox_container,omitempty"` // Container name when running in sandbox.
// SSH remote support
SSHHost string `json:"ssh_host,omitempty"`
SSHRemotePath string `json:"ssh_remote_path,omitempty"`
// TmuxSocketName is the tmux `-L <name>` socket selector captured when
// this instance was created (v1.7.50+, issue #687). Empty string keeps
// the pre-v1.7.50 behavior of targeting the user's default tmux server
// — zero change for existing installations.
//
// Precedence at creation time: the `--tmux-socket` CLI flag on
// `agent-deck add` / `agent-deck launch` wins, else
// `[tmux].socket_name` from config.toml, else empty. Once persisted,
// this value is IMMUTABLE — lifecycle operations (start/stop/restart/
// revive) MUST target this same socket even if the installation-wide
// config is later edited. Mixing sockets would leave the session
// orphaned on an unreachable tmux server.
TmuxSocketName string `json:"tmux_socket_name,omitempty"`
// MCP tracking - which MCPs were loaded when session started/restarted
// Used to detect pending MCPs (added after session start) and stale MCPs (removed but still running)
LoadedMCPNames []string `json:"loaded_mcp_names,omitempty"`
// TrackedMCPPIDs holds the OS PIDs of stdio MCP children spawned for
// this session (issue #965). Session stop must SIGTERM (then SIGKILL
// after a grace period) each PID so children aren't reparented to
// PID 1 and leaked. Mutated only via RegisterMCPChild /
// UnregisterMCPChild to keep concurrent access safe.
TrackedMCPPIDs []int `json:"tracked_mcp_pids,omitempty"`
// Channels are Claude Code plugin-channel ids (e.g. "plugin:telegram@user/repo").
// When non-empty on a claude session, buildClaudeExtraFlags emits
// `--channels <csv>` so the session subscribes to inbound plugin messages.
// Without this flag the channel plugin runs as a plain MCP (tools only,
// no inbound delivery) which silently drops Telegram/Discord/Slack
// messages on conductor restart.
Channels []string `json:"channels,omitempty"`
// Plugins is the catalog-key list of Claude Code plugins enabled for
// this session via `agent-deck add --plugin <name>` /
// `session set <id> plugins <csv>`. Names are short catalog keys (NOT
// fully-qualified `<name>@<source>` ids) and resolve through the
// [plugins.<name>] table in ~/.agent-deck/config.toml at spawn time.
// When non-empty on a claude session, EnsureWorkerScratchConfigDir
// writes enabledPlugins[<id>] = true into the scratch settings.json so
// the plugin loads only for this session, not globally.
// RFC: docs/rfc/PLUGIN_ATTACH.md.
Plugins []string `json:"plugins,omitempty"`
// InheritTelegramEnv is the explicit opt-in for #1133: when true, a
// non-channel-owning claude child KEEPS the conductor's TELEGRAM_*
// env vars (TELEGRAM_STATE_DIR, TELEGRAM_BOT_TOKEN, etc.). Default
// false strips them so a child can't spawn a duplicate `bun telegram`
// poller that races the conductor for getUpdates (Telegram 409
// Conflict + dropped inbound messages). CLI flag:
// `--inherit-telegram-env` on `agent-deck launch`. Rare use case;
// existing behavior is preserved when the flag is absent.
InheritTelegramEnv bool `json:"inherit_telegram_env,omitempty"`
// PluginChannelLinkDisabled opts the session out of the catalog-driven
// auto-link between Plugins and Channels (RFC §4.7). When true, an
// `--plugin foo` whose catalog entry has EmitsChannel=true does NOT
// auto-add `plugin:foo@source` to Channels. Useful for tools-only
// usage of channel-emitting plugins. CLI flag: `--no-channel-link`.
PluginChannelLinkDisabled bool `json:"plugin_channel_link_disabled,omitempty"`
// AutoLinkedChannels is the persisted set of channel ids that
// syncPluginChannels last added via the auto-link mechanism. Lets
// reconciliation distinguish "channel I owned" from "channel the
// user added manually" — without it, a plugin removed from the
// catalog or an opt-out toggle would leave stale autolinks behind
// (G4 / C2). Updated on every Plugins mutation; never written
// directly by users.
AutoLinkedChannels []string `json:"auto_linked_channels,omitempty"`
// WorkerScratchConfigDir is the ephemeral CLAUDE_CONFIG_DIR prepared
// for a non-conductor claude worker (issue #59, v1.7.68). The
// scratch dir copies the ambient profile's settings.json with the
// telegram plugin explicitly disabled, symlinks the rest of the
// profile, and is cleaned up on session stop/remove. Empty for
// conductor sessions, explicit telegram channel owners, and
// non-claude tools — they use the ambient profile as-is.
WorkerScratchConfigDir string `json:"worker_scratch_config_dir,omitempty"`
// IdleTimeoutSecs is the auto-stop threshold (#1143). When > 0, a central
// watcher poll triggers Kill() if the tmux pane content stays unchanged
// for this many seconds. 0 = disabled (current behavior). Default is 0
// so existing sessions are unaffected on upgrade.
IdleTimeoutSecs int64 `json:"idle_timeout_secs,omitempty"`
// IsForkAwaitingStart signals that this instance was produced by a
// fork builder and must run a pre-built fork command verbatim on the
// first Start() (#745). Claude fork targets usually store that command
// in Command. Pi fork targets keep Command as the normal restart
// command (so later restarts use --continue) and store the first-start
// command in ForkStartCommand instead.
//
// Transient (json:"-"): persisting this would cause a restart of the
// forked session to re-emit the tool-specific fork command and
// double-count the parent transcript.
IsForkAwaitingStart bool `json:"-"`
// ForkStartCommand optionally carries the pre-built command to run while
// IsForkAwaitingStart is true. When empty, Start() falls back to Command
// for backwards compatibility with Claude fork targets.
ForkStartCommand string `json:"-"`
// ExtraArgs are user-supplied claude CLI tokens appended verbatim to every
// start/resume/fork command (e.g. ["--agent","reviewer","--model","opus"]).
// Each token is shellescape-quoted on emission so values with spaces
// survive the bash -c wrapper.
ExtraArgs []string `json:"extra_args,omitempty"`
// ExitToShell is the per-session override for the [shell] exit_to_shell
// toggle (issue #1161). nil → inherit the global config default (off);
// non-nil → force on/off for this session regardless of config. When on
// and the tool is a built-in agent, the spawn command is wrapped so that
// exiting the agent drops the pane to an interactive shell at the same cwd.
ExitToShell *bool `json:"exit_to_shell,omitempty"`
// LaunchShell is the per-session override for the [shell] launch_shell
// toggle (issue #1218). nil → inherit the global config default (off);
// non-nil → force on/off for this session regardless of config. When on,
// the spawn command is wrapped with "$SHELL -l -c '<cmd>'" so that
// environment variables from ~/.zshrc, ~/.bashrc etc. are available to
// the agent process. This solves MCP config {env:VAR} failures when
// launching from the TUI without going through the user's shell.
LaunchShell *bool `json:"launch_shell,omitempty"`
// StartupQuery is the claude-code positional "startup query" (#725,
// v1.7.67). Set from the new-session dialog's "Start query" field and
// emitted as a single shell-quoted positional arg on the claude
// new-session command line only.
//
// Per-session, NEVER persisted — the `json:"-"` tag is load-bearing.
// On Restart/Resume the field is empty, so the query does NOT replay.
// This is the whole point of having a dedicated field instead of
// overloading ExtraArgs (which persists and space-splits).
StartupQuery string `json:"-"`
// ToolOptions stores tool-specific launch options (Claude, Codex, Gemini, etc.)
// JSON structure: {"tool": "claude", "options": {...}}
ToolOptionsJSON json.RawMessage `json:"tool_options,omitempty"`
// SkipMCPRegenerate skips .mcp.json regeneration on next Restart()
// Set by MCP dialog Apply() to avoid race condition where Apply writes
// config then Restart immediately overwrites it with different pool state
SkipMCPRegenerate bool `json:"-"` // Don't persist, transient flag
// contains filtered or unexported fields
}
Instance represents a single agent/shell session
func DiscoverExistingTmuxSessions ¶
DiscoverExistingTmuxSessions finds all tmux sessions and converts them to instances
func FilterByQuery ¶
FilterByQuery filters sessions by title, project path, tool, or status Supports status filters: "waiting", "running", "idle", "error"
func FilterInstancesByArchive ¶ added in v1.9.55
FilterInstancesByArchive returns instances whose archive state matches archived.
func FindNextQueued ¶ added in v1.9.1
FindNextQueued returns the oldest queued instance in the given group, or nil if none are queued. "Oldest" is by CreatedAt (FIFO drain order).
func NewInstance ¶
NewInstance creates a new session instance
func NewInstanceWithGroup ¶
NewInstanceWithGroup creates a new session instance with explicit group
func NewInstanceWithGroupAndTool ¶ added in v0.4.1
NewInstanceWithGroupAndTool creates a new session with explicit group and tool
func NewInstanceWithTool ¶ added in v0.4.1
NewInstanceWithTool creates a new session with tool-specific initialization
func (*Instance) AllProjectPaths ¶ added in v0.26.2
AllProjectPaths returns all project paths: [ProjectPath] + AdditionalPaths.
func (*Instance) ApplyLaunchModel ¶ added in v1.9.14
ApplyLaunchModel stores a per-session model override in the tool-specific field that the relevant command builder already reads on start/restart.
func (*Instance) CachedSubstate ¶ added in v1.9.66
CachedSubstate returns the last substate computed by a prior status check WITHOUT capturing the pane. Use it on the TUI render hot path; the background status loop keeps the value fresh.
func (*Instance) CanForkCodex ¶ added in v1.9.50
CanForkCodex reports whether this Codex session can be forked. Forkability requires a flushed on-disk rollout for the captured session id — the same invariant buildCodexCommand uses to gate `codex resume` (#756). `codex fork` is a newer codex CLI subcommand; if the installed binary predates it the launched command fails into a recoverable error state.
func (*Instance) CanForkOpenCode ¶ added in v0.8.99
CanForkOpenCode returns true if this OpenCode session can be forked
func (*Instance) CanForkPi ¶ added in v1.9.48
CanForkPi returns true if this Pi session can be forked by Agent Deck.
func (*Instance) CanRestart ¶ added in v0.4.1
CanRestart returns true if the session can be restarted For Claude sessions with known ID: can always restart (interrupt and resume) For Gemini sessions with known ID: can always restart (interrupt and resume) For OpenCode sessions with known ID: can always restart (interrupt and resume) For Codex sessions with known ID: can always restart (interrupt and resume) For custom tools with session resume config: can restart if session ID available For other sessions: only if dead/error state
func (*Instance) CanRestartFresh ¶ added in v1.7.21
CanRestartFresh returns true when the session has a known tool session binding that can be intentionally discarded to start with a new session ID.
func (*Instance) CanRestartGeneric ¶ added in v0.8.27
CanRestartGeneric returns true if a custom tool can be restarted with session resume
func (*Instance) CaptureLoadedMCPs ¶ added in v0.5.3
func (i *Instance) CaptureLoadedMCPs()
CaptureLoadedMCPs captures the current MCP names as the "loaded" state This should be called when a session starts or restarts, so we can track which MCPs are actually loaded in the running Claude session vs just configured
func (*Instance) ClaudeSessionIDCollidesWith ¶ added in v1.9.53
ClaudeSessionIDCollidesWith reports whether another LIVE instance in peers would resolve to the SAME transcript as this instance: it shares this instance's (non-empty) ClaudeSessionID AND resolves to the same transcript directory (same Claude config dir + same encoded project path). A many-to-one session-id → live-instance mapping on one transcript is the data-integrity hazard #1349 describes: two live instances pointed at one transcript would cross-route input/output. Two instances that happen to share a session id but resolve to different transcript dirs (different project/config) are NOT a collision and are not blocked.
func (*Instance) CleanupMultiRepoTempDir ¶ added in v0.26.2
CleanupMultiRepoTempDir removes the multi-repo temporary directory.
func (*Instance) CleanupWorkerScratchConfigDir ¶ added in v1.7.68
func (i *Instance) CleanupWorkerScratchConfigDir()
CleanupWorkerScratchConfigDir removes the scratch dir. Best-effort.
func (*Instance) ClearHookStatus ¶ added in v0.16.0
func (i *Instance) ClearHookStatus()
ClearHookStatus resets the hook-based status and removes the persisted hook record, forcing the next UpdateStatus() to fall through to polling. Used when the user manually overrides status (e.g., pressing 'u' to unacknowledge after an Escape interrupt where the Stop hook didn't fire).
func (*Instance) ClearLaunchModel ¶ added in v1.9.62
ClearLaunchModel removes any per-session model override so the session falls back to the configured default ([claude].default_model, etc.) on the next start/restart (#1436). Mirrors ApplyLaunchModel across tools; a no-op when no override is set.
func (*Instance) ClearParent ¶ added in v0.7.0
func (inst *Instance) ClearParent()
ClearParent removes the parent session link
func (*Instance) ConductorClearOnCompact ¶ added in v0.19.15
ConductorClearOnCompact checks if this conductor instance has clear_on_compact enabled. Extracts the conductor name from the session title ("conductor-{NAME}"), loads meta.json, and returns the setting (defaults to true). Returns false if the title doesn't match conductor format, since the caller should not enable clear-on-compact for non-conductor sessions.
func (*Instance) ConsumeCodexRestartWarning ¶ added in v0.21.0
ConsumeCodexRestartWarning returns and clears any pending Codex restart warning.
func (*Instance) CreateForkedCodexInstanceWithOptions ¶ added in v1.9.50
func (i *Instance) CreateForkedCodexInstanceWithOptions( newTitle, newGroupPath string, opts *ClaudeOptions, ) (*Instance, string, error)
CreateForkedCodexInstanceWithOptions creates a forked Codex instance. Mirrors CreateForkedPiInstanceWithOptions: opts is the shared worktree carrier (only WorkDir/Worktree* consumed); launch is deferred via ForkStartCommand.
func (*Instance) CreateForkedInstance ¶ added in v0.4.1
CreateForkedInstance creates a new Instance configured for forking Deprecated: Use CreateForkedInstanceWithOptions instead
func (*Instance) CreateForkedInstanceForTool ¶ added in v1.9.50
func (i *Instance) CreateForkedInstanceForTool(newTitle, newGroupPath string, opts *ClaudeOptions) (*Instance, string, error)
CreateForkedInstanceForTool creates a forked instance using the correct tool-specific fork implementation. opts is the shared fork carrier for worktree fields; non-Claude tool options continue to come from global config.
func (*Instance) CreateForkedInstanceWithOptions ¶ added in v0.8.39
func (i *Instance) CreateForkedInstanceWithOptions( newTitle, newGroupPath string, opts *ClaudeOptions, ) (*Instance, string, error)
CreateForkedInstanceWithOptions creates a new Instance configured for forking with custom options
func (*Instance) CreateForkedOpenCodeInstance ¶ added in v0.8.99
func (i *Instance) CreateForkedOpenCodeInstance(newTitle, newGroupPath string) (*Instance, string, error)
CreateForkedOpenCodeInstance creates a new Instance configured for forking an OpenCode session Deprecated: Use CreateForkedOpenCodeInstanceWithOptions instead.
func (*Instance) CreateForkedOpenCodeInstanceWithOptions ¶ added in v0.12.1
func (i *Instance) CreateForkedOpenCodeInstanceWithOptions( newTitle, newGroupPath string, opts *OpenCodeOptions, ) (*Instance, string, error)
CreateForkedOpenCodeInstanceWithOptions creates a new Instance configured for forking with custom options
func (*Instance) CreateForkedOpenCodeInstanceWithOptionsAndWorkDir ¶ added in v1.9.50
func (i *Instance) CreateForkedOpenCodeInstanceWithOptionsAndWorkDir( newTitle, newGroupPath string, opts *OpenCodeOptions, workDir, worktreeRepoRoot, worktreeBranch string, ) (*Instance, string, error)
CreateForkedOpenCodeInstanceWithOptionsAndWorkDir creates a forked OpenCode instance rooted at workDir and copies worktree metadata when worktreeRepoRoot is set.
func (*Instance) CreateForkedPiInstance ¶ added in v1.9.48
CreateForkedPiInstance creates a new Instance configured for forking a Pi session. Deprecated: Use CreateForkedPiInstanceWithOptions instead.
func (*Instance) CreateForkedPiInstanceWithOptions ¶ added in v1.9.48
func (i *Instance) CreateForkedPiInstanceWithOptions( newTitle, newGroupPath string, opts *ClaudeOptions, ) (*Instance, string, error)
CreateForkedPiInstanceWithOptions creates a new Instance configured for forking a Pi session. The opts parameter is accepted for the shared fork worktree flow; only WorkDir and Worktree* fields are consumed for Pi.
func (*Instance) DetectCodexSession ¶ added in v0.8.76
func (i *Instance) DetectCodexSession()
DetectCodexSession is the public wrapper for async Codex session detection Call this for restored sessions that don't have a session ID yet
func (*Instance) DetectOpenCodeSession ¶ added in v0.8.72
func (i *Instance) DetectOpenCodeSession()
DetectOpenCodeSession is the public wrapper for async OpenCode session detection Call this for restored sessions that don't have a session ID yet
func (*Instance) DisplayLastActivityTime ¶ added in v1.10.9
DisplayLastActivityTime returns the timestamp to show as "last active" in the UI. It intentionally differs from GetLastActivityTime: that method returns the tmux tracker's raw lastChangeTime (which is seeded with time.Now() when the tracker is lazily created, so it leaks ~ the TUI load time for sessions that never confirm real activity — e.g. error/idle/ stopped panes) and also feeds OpenCode rotation windows, so its semantics must not change.
Here we consult only CONFIRMED activity (LastObservedActivity guards on realActivityConfirmed). When none has been observed we fall back to the persisted last-accessed time — matching what the web serves — and finally to CreatedAt. This keeps the TUI "⏱ last active" line in agreement with the web instead of resetting to the most recent TUI load.
func (*Instance) DisplaySessionID ¶ added in v1.9.51
DisplaySessionID returns the session ID the PREVIEW pane surfaces for this instance's tool, mirroring the per-tool branching in the right-pane render so a copy of the preview info carries the same ID the user sees. Returns "" when no session ID is known for the tool.
func (*Instance) EffectiveWorkingDir ¶ added in v0.26.2
EffectiveWorkingDir returns the working directory for this session. For multi-repo sessions, this is the temp dir; otherwise the ProjectPath.
func (*Instance) EnsurePluginsInstalled ¶ added in v1.9.2
EnsurePluginsInstalled shells out to `claude plugin install` for every catalog plugin in i.Plugins whose code isn't already on disk under sourceProfileDir. Best-effort: always returns nil. Concurrent installs across sessions serialize via a per-(profile, plugin) lock under `~/.agent-deck/locks/`. The catalog `auto_install` flag is NOT consulted — explicit attach is itself the consent signal.
func (*Instance) EnsureWorkerScratchConfigDir ¶ added in v1.7.68
EnsureWorkerScratchConfigDir idempotently prepares the scratch CLAUDE_CONFIG_DIR. Returns "" (no error) when no scratch is needed — callers treat that as "use the ambient profile". The scratch mirrors sourceProfileDir via symlinks and rewrites settings.json with the deny+allow overlay. Source absence is fine — we emit a minimal settings.json.
func (*Instance) ForceNextStatusCheck ¶ added in v0.16.0
func (i *Instance) ForceNextStatusCheck()
ForceNextStatusCheck clears the idle polling optimization so the next UpdateStatus() performs a full check instead of short-circuiting. Call this before UpdateStatus() when a status-affecting change was made externally (e.g. the u key toggling acknowledged state).
func (*Instance) Fork ¶ added in v0.4.1
Fork returns the command to create a forked Claude session Uses capture-resume pattern: starts fork in print mode to get new session ID, stores in tmux environment, then resumes interactively Deprecated: Use ForkWithOptions instead
func (*Instance) ForkOpenCode ¶ added in v0.8.99
ForkOpenCode returns the command to create a forked OpenCode session. Uses OpenCode's native `--fork` flag to branch the parent session into a new session with its own id (discovered asynchronously after launch). Deprecated: Use ForkOpenCodeWithOptions instead.
func (*Instance) ForkOpenCodeWithOptions ¶ added in v0.12.1
func (i *Instance) ForkOpenCodeWithOptions(newTitle, newGroupPath string, opts *OpenCodeOptions) (string, error)
ForkOpenCodeWithOptions returns the command to create a forked OpenCode session with custom options. Uses OpenCode's native `opencode -s <parent-id> --fork`, which branches the parent transcript into a fresh session while leaving the parent intact, plus any model/agent flags.
func (*Instance) ForkWithOptions ¶ added in v0.8.39
func (i *Instance) ForkWithOptions(newTitle, newGroupPath string, opts *ClaudeOptions) (string, error)
ForkWithOptions returns the command to create a forked Claude session with custom options Uses capture-resume pattern: starts fork in print mode to get new session ID, stores in tmux environment, then resumes interactively
func (*Instance) GetActualWorkDir ¶ added in v0.4.1
GetActualWorkDir returns the actual working directory from tmux, or falls back to ProjectPath
func (*Instance) GetAutoName ¶ added in v1.9.58
GetAutoName reports whether this session should display a captured/live task description instead of its machine-generated handle. Thread-safe.
func (*Instance) GetAutoNameDescription ¶ added in v1.9.58
GetAutoNameDescription returns the last captured Claude task description for an AutoName session (empty if none captured yet). Thread-safe.
func (*Instance) GetClaudeOptions ¶ added in v0.8.39
func (i *Instance) GetClaudeOptions() *ClaudeOptions
GetClaudeOptions returns Claude-specific options, or nil if not set
func (*Instance) GetCodexOptions ¶ added in v0.10.18
func (i *Instance) GetCodexOptions() *CodexOptions
GetCodexOptions returns Codex-specific options, or nil if not set
func (*Instance) GetCopilotOptions ¶ added in v1.9.20
func (i *Instance) GetCopilotOptions() *CopilotOptions
GetCopilotOptions returns CopilotOptions from the instance's tool options.
func (*Instance) GetGenericSessionID ¶ added in v0.8.27
GetGenericSessionID gets session ID from tmux environment for a custom tool Uses the session_id_env field from tool config
func (*Instance) GetHermesOptions ¶ added in v1.9.47
func (i *Instance) GetHermesOptions() *HermesOptions
GetHermesOptions returns Hermes-specific options from ToolOptionsJSON, or nil if not set.
func (*Instance) GetHookStatus ¶ added in v0.16.0
GetHookStatus returns the current hook-based status and its freshness. Freshness window is tool-specific.
func (*Instance) GetJSONLPath ¶ added in v0.8.27
GetJSONLPath returns the path to the Claude session JSONL file for analytics. Returns empty string if this is not a Claude session or the transcript is absent.
func (*Instance) GetJSONLPathChecked ¶ added in v1.9.53
GetJSONLPathChecked is the collision-aware variant of GetJSONLPath (issue #1349 defense-in-depth #2). Given the set of instances it shares a profile with, it refuses to resolve a transcript path when this instance's ClaudeSessionID collides with another LIVE instance's ClaudeSessionID — because two live instances mapped to one session-id would otherwise read the same transcript, corrupting routing. When there is no collision it delegates to GetJSONLPath.
func (*Instance) GetLastActivityTime ¶ added in v0.5.6
GetLastActivityTime returns when the session was last active (content changed) Returns CreatedAt if no activity has been tracked yet
func (*Instance) GetLastResponse ¶ added in v0.7.0
func (i *Instance) GetLastResponse() (*ResponseOutput, error)
GetLastResponse returns the last assistant response from the session For Claude: Parses the JSONL file for the last assistant message For Gemini: Parses the JSON session file for the last assistant message For Codex/Others: Attempts to parse terminal output
func (*Instance) GetLastResponseBestEffort ¶ added in v0.19.10
func (i *Instance) GetLastResponseBestEffort() (*ResponseOutput, error)
GetLastResponseBestEffort returns the last assistant response with fallback logic intended for CLI read paths (like `session output`) where we prefer useful output over hard errors.
Behavior for Claude: 1. Try structured JSONL read via stored ClaudeSessionID. 2. Refresh ID from tmux env and retry. 3. Fallback to terminal parsing. 4. If still unavailable, return an empty response (no error).
Behavior for Gemini (mirrors Claude): 1. Try structured JSON read via stored GeminiSessionID. 2. Refresh ID from tmux env and retry. 3. Scan disk for latest session and retry. 4. Fallback to terminal parsing. 5. If still unavailable, return an empty response (no error).
func (*Instance) GetLastResponseBestEffortChecked ¶ added in v1.9.58
func (i *Instance) GetLastResponseBestEffortChecked(peers []*Instance) (*ResponseOutput, error)
GetLastResponseBestEffortChecked is the collision-aware variant of GetLastResponseBestEffort (issue #1400). `session output` (including -q) parses the transcript that ClaudeSessionID resolves to; when multiple LIVE instances share one claude_session_id they all resolve to the SAME transcript and return byte-identical "last responses". Given the instance's profile peers, this refuses the read with the same collision semantics #1352 gave `session output --stream` (GetJSONLPathChecked: live peers sharing both the session id and the transcript dir), instead of silently returning another session's output. Non-Claude tools and the no-collision case delegate to GetLastResponseBestEffort unchanged.
func (*Instance) GetMCPInfo ¶ added in v0.5.3
GetMCPInfo returns MCP server information for this session. Returns nil if not a Claude-compatible, Gemini, or Cursor session. Hermes is intentionally excluded: it uses its own ~/.hermes/config.yaml `mcp_servers:` schema (user-scoped, YAML), not Claude's project-scoped .mcp.json — agent-deck does not manage it yet.
func (*Instance) GetOpenCodeOptions ¶ added in v0.12.1
func (i *Instance) GetOpenCodeOptions() *OpenCodeOptions
GetOpenCodeOptions returns OpenCode-specific options, or nil if not set
func (*Instance) GetOpenCodePort ¶ added in v1.10.10
GetOpenCodePort returns the event-server port with lock protection.
func (*Instance) GetSessionIDFromTmux ¶ added in v0.5.0
GetSessionIDFromTmux reads Claude session ID from tmux environment This is the primary method for sessions started with the capture-resume pattern
func (*Instance) GetStatusThreadSafe ¶ added in v0.10.6
GetStatusThreadSafe returns the session status with read-lock protection. Use this when reading Status from a goroutine concurrent with backgroundStatusUpdate.
func (*Instance) GetTmuxSession ¶
GetTmuxSession returns the tmux session object
func (*Instance) GetToolThreadSafe ¶ added in v0.10.6
GetToolThreadSafe returns the tool name with read-lock protection.
func (*Instance) GetWaitingSince ¶ added in v0.8.50
GetWaitingSince returns when the session transitioned to waiting status Used for sorting notification bar (newest waiting sessions first)
func (*Instance) HasUpdated ¶
HasUpdated checks if there's new output since last check
func (*Instance) InvalidateProjectMCPIntegrationsCache ¶ added in v1.9.47
func (i *Instance) InvalidateProjectMCPIntegrationsCache()
InvalidateProjectMCPIntegrationsCache clears MCP read caches for this instance's project.
func (*Instance) IsArchived ¶ added in v1.9.55
IsArchived reports whether the session is in the user archive.
func (*Instance) IsMaestro ¶ added in v1.9.56
IsMaestro reports whether this instance is the fleet supervisor. Exact match only: worker sessions titled "maestro-<something>" are regular sessions and must not be detected as the supervisor.
func (*Instance) IsMultiRepo ¶ added in v0.26.2
IsMultiRepo returns true if this session has multi-repo mode enabled.
func (*Instance) IsSSH ¶ added in v0.20.0
IsSSH returns true if this instance runs on a remote host via SSH.
func (*Instance) IsSandboxed ¶ added in v0.19.17
IsSandboxed returns true if this instance is configured to run in a Docker sandbox.
func (*Instance) IsSubSession ¶ added in v0.7.0
IsSubSession returns true if this session has a parent
func (*Instance) IsWorktree ¶ added in v0.8.22
IsWorktree returns true if this session is running in a git worktree
func (*Instance) Kill ¶
Kill terminates the tmux session and cleans up sandbox container if present.
func (*Instance) KillAndWait ¶ added in v1.7.68
KillAndWait is the synchronous companion to Kill. It performs the same teardown AND blocks until the pane process tree has been verified dead (SIGTERM → SIGKILL escalation inline, not in a background goroutine). Callers in short-lived CLI processes (`agent-deck remove`, `agent-deck session remove`) MUST use this variant — see issue #59 (v1.7.68). The TUI and web callers can keep using Kill for the non-blocking path.
func (*Instance) LastObservedActivity ¶ added in v1.9.48
LastObservedActivity returns the last time the tmux tracker confirmed a real busy spike for this session, and a bool that is false when no confirmation has happened (the instance has no tmux session, or the tracker has never observed activity). When the bool is false the time value is zero.
func (*Instance) LaunchModelID ¶ added in v1.9.14
LaunchModelID returns the exact per-session model override that will be passed to the underlying tool on start/restart. Empty means tool default.
func (*Instance) LaunchModelInfo ¶ added in v1.9.14
LaunchModelInfo returns normalized status fields for the session's model override. Empty ModelID means the tool's configured default is in effect.
func (*Instance) MCPGlobalConfigPath ¶ added in v1.9.47
MCPGlobalConfigPath returns the global MCP file path for this instance's tool.
func (*Instance) MCPInfoForLocalAttach ¶ added in v1.9.47
MCPInfoForLocalAttach returns MCP info for local attach/detach for this instance.
func (*Instance) MCPLocalConfigPath ¶ added in v1.9.47
MCPLocalConfigPath returns the project-local MCP file path for this instance's tool.
func (*Instance) MarkAccessed ¶ added in v0.5.3
func (inst *Instance) MarkAccessed()
MarkAccessed updates the LastAccessedAt timestamp to now
func (*Instance) NeedsWorkerScratchConfigDir ¶ added in v1.7.68
NeedsWorkerScratchConfigDir is true when a scratch CLAUDE_CONFIG_DIR must be prepared at spawn. Reasons:
- Telegram poller defense (v1.7.68 / #759) — pin telegramPluginID off.
- Per-session plugin enablement (RFC docs/rfc/PLUGIN_ATTACH.md) — write enabledPlugins[<id>] = true without contaminating the ambient profile or peer sessions.
- GLOBAL_ANTIPATTERN guard (issue #941) — channel-owning conductor with `enabledPlugins.telegram=true` already in the ambient settings.json. The TelegramValidator surfaces this as DOUBLE_LOAD but warnings don't prevent the spawn; the scratch pins telegram off so --channels is the only activation source and exactly one bun poller runs.
When multiple reasons fire, EnsureWorkerScratchConfigDir combines the deny+allow lists.
func (*Instance) OpenContainerShell ¶ added in v0.19.17
OpenContainerShell creates a tmux session running an interactive shell inside the sandbox container. Returns the tmux session name for attaching. Uses /bin/sh for portability (not all images have bash).
func (*Instance) PostStartSync ¶ added in v0.8.97
PostStartSync captures session IDs from tmux environment after Start() or Restart(). Designed for CLI commands that exit after starting. The TUI doesn't need this because its background worker handles detection.
For Claude: polls tmux env for CLAUDE_SESSION_ID (set by bash uuidgen before exec). For Gemini: reads session ID from filesystem. For OpenCode/Codex: no-op (async goroutine detection, too slow for sync CLI).
func (*Instance) PreviewFull ¶
PreviewFull returns all terminal output
func (*Instance) PreviewWindowFull ¶ added in v0.21.0
PreviewWindowFull returns the full scrollback of a specific tmux window.
func (*Instance) ReconcileTitleFromClaude ¶ added in v1.9.48
ReconcileTitleFromClaude refreshes i.Title from the agent's current Claude session name. It is the shared core behind both the hook-event sync (#572) and the on-attach reconcile (#1114 follow-up): Claude's /rename fires no agent-deck hook, so an idle session's title and iTerm2 badge stay stale until the next turn boundary — reconciling on attach makes detach/reattach a reliable manual refresh.
Honors the global sync_title switch and the per-session TitleLocked flag (so conductor titles like "SCRUM-351" survive Claude's own /rename). On a real change it mutates the in-memory instance (Title + tmux display name) and drops the iTerm2 badge-update signal so the attach-side WatchBadgeUpdates catch-up re-emits the fresh name instead of clobbering it with the old one.
Returns the new name and true iff the title changed; the CALLER is responsible for persisting the instance to storage. The on-attach caller (internal/ui/home.go) saves under the same in-process lock it just read TitleLocked from, so applying side effects immediately here is safe for that caller; the hook-triggered sync is NOT that caller — see ResolveTitleFromClaude.
func (*Instance) RefreshLiveSessionIDs ¶ added in v1.7.13
func (i *Instance) RefreshLiveSessionIDs()
RefreshLiveSessionIDs re-reads tool-specific session identifiers from the live tmux environment and updates the instance's stored IDs when a newer non-empty value is found. Safe no-op when tmuxSession is nil or the tool has no live-env handle.
Call this before reads that must reflect the CURRENT conversation (e.g. TUI cross-session send-output, issue #598). Reads that tolerate stale data (status polling) don't need it.
func (*Instance) RegisterMCPChild ¶ added in v1.9.9
RegisterMCPChild records the OS PID of a stdio MCP child spawned for this session. Session stop iterates these PIDs and signals each (SIGTERM → SIGKILL) to prevent the issue-#965 orphan accumulation where MCP children get reparented to PID 1.
Safe to call concurrently. Passing pid <= 0 is a no-op.
func (*Instance) ResolveTitleFromClaude ¶ added in v1.10.9
ResolveTitleFromClaude is the pure decision half of ReconcileTitleFromClaude: it answers "does Claude's session name warrant a rename" without mutating i or touching tmux. Honors the same sync_title switch and TitleLocked flag.
Split out for the hook-triggered sync (cmd/agent-deck/hook_name_sync.go), which persists via a conditional UPDATE ... WHERE title_locked = 0 that can legitimately no-op if a user rename landed and locked the title first. The combined ReconcileTitleFromClaude fires tmux/badge side effects unconditionally on a decision to rename, before the caller's persistence attempt is even known to have succeeded — a hook whose write gets rejected would still have already overwritten the live tmux window title and iTerm badge with Claude's name, leaving the terminal chrome out of sync with the correctly-preserved stored title. Callers in that situation should call this instead and only run the equivalent of ReconcileTitleFromClaude's side effects once their own write is confirmed applied.
func (*Instance) Restart ¶ added in v0.4.1
Restart restarts the Claude session For Claude sessions with known ID: sends Ctrl+C twice and resume command to existing session For dead sessions or unknown ID: recreates the tmux session
Issue #1040: gated by acquireInstanceSpawnLock plus a "spawned-while- we-waited" stamp so concurrent callers (TUI poller + RC-exit handler in-process; multiple `agent-deck session start` CLI invocations cross-process) cannot each race to recreate a tmux session for the same instance. A legitimate manual restart still proceeds because the stamp from any prior spawn pre-dates the new caller's beforeLock.
func (*Instance) RestartFresh ¶ added in v1.7.21
RestartFresh restarts the current tool without resuming the existing tool session. This recreates the tmux session and clears the stored tool session binding first, so the next start gets a brand-new tool session ID.
func (*Instance) RestartWithEnv ¶ added in v1.10.9
RestartWithEnv restarts the session with one-shot environment overrides. The values apply to the replacement process only and are not persisted in the Instance or tmux session environment for future restarts.
func (*Instance) SetAcknowledgedFromShared ¶ added in v0.11.0
SetAcknowledgedFromShared applies an acknowledgment from another TUI instance (read from SQLite). This transitions a YELLOW (waiting) session to GRAY (idle) without requiring the user to interact with this specific TUI instance.
func (*Instance) SetAutoName ¶ added in v1.9.58
SetAutoName updates whether this session should display a captured/live task description instead of its machine-generated handle. Thread-safe.
func (*Instance) SetAutoNameDescription ¶ added in v1.9.58
SetAutoNameDescription records the latest Claude task description for an AutoName session so it can be persisted and shown on reopen. Thread-safe.
func (*Instance) SetClaudeOptions ¶ added in v0.8.39
func (i *Instance) SetClaudeOptions(opts *ClaudeOptions) error
SetClaudeOptions stores Claude-specific options
func (*Instance) SetCodexOptions ¶ added in v0.10.18
func (i *Instance) SetCodexOptions(opts *CodexOptions) error
SetCodexOptions stores Codex-specific options
func (*Instance) SetGeminiModel ¶ added in v0.8.79
SetGeminiModel sets the Gemini model for this session and triggers a restart if running.
func (*Instance) SetGeminiYoloMode ¶ added in v0.8.50
SetGeminiYoloMode sets the YOLO mode for Gemini and syncs it to the tmux environment. This ensures the background status worker sees the correct state during restarts.
func (*Instance) SetHermesOptions ¶ added in v1.9.47
func (i *Instance) SetHermesOptions(opts *HermesOptions) error
SetHermesOptions stores Hermes-specific options into ToolOptionsJSON.
func (*Instance) SetOpenCodeOptions ¶ added in v0.12.1
func (i *Instance) SetOpenCodeOptions(opts *OpenCodeOptions) error
SetOpenCodeOptions stores OpenCode-specific options
func (*Instance) SetParentWithPath ¶ added in v0.8.34
SetParentWithPath sets both parent session ID and parent's project path The project path is used to grant subagent access via --add-dir
func (*Instance) SetStatusThreadSafe ¶ added in v0.10.6
SetStatusThreadSafe sets the session status with write-lock protection.
func (*Instance) SetTmuxSessionForTest ¶ added in v1.8.3
SetTmuxSessionForTest assigns the unexported tmuxSession field. Tests in other packages (notably internal/ui) need this to construct an Instance with a *tmux.Session attached without going through storage hydration or a real tmux server.
Do not call from non-test code; production paths populate tmuxSession via Start (instance.go) or storage.LoadWithGroups (storage.go).
func (*Instance) SetToolThreadSafe ¶ added in v0.10.6
SetToolThreadSafe sets the tool name with write-lock protection.
func (*Instance) SpawnFailure ¶ added in v1.10.9
func (i *Instance) SpawnFailure() *SpawnFailureRecord
SpawnFailure returns the recorded spawn-failure diagnostic for this instance, or nil when there is none. Exported for CLI surfaces (`session show`) that want to explain a bare "error" status (#1580).
func (*Instance) Start ¶
Start starts the session in tmux.
Issue #1040: gated by acquireInstanceSpawnLock plus a "spawned-while- we-waited" stamp so concurrent `agent-deck session start <id>` invocations after a Claude exit don't each fall through the "tmux session does not exist" gate and spawn parallel sessions. The lock and gate are inlined here (rather than wrapping the whole body in a SpawnAttempt helper) to preserve the structural-grep contract that checks Start()'s body for the #745 IsForkAwaitingStart guard.
func (*Instance) StartWithMessage ¶ added in v0.6.2
StartWithMessage starts the session and sends an initial message when ready The message is sent synchronously after detecting the agent's prompt This approach is more reliable than embedding send logic in the tmux command Works for Claude, Gemini, OpenCode, and other agents
Issue #1040: same per-instance spawn lock as Start() — a concurrent `launch -m "..."` racing with a poller-triggered Start() must not produce two parallel tmux sessions.
func (*Instance) StopServiceUnit ¶ added in v1.7.21
StopServiceUnit best-effort stops + resets-failed the transient systemd-user service unit associated with this instance's tmux server (if LaunchAs=service was used). Intended for the remove/delete code path ONLY — NOT for restart, which needs the unit to persist so it can re-spawn tmux.
No-ops on non-systemd hosts. Returns nil when the unit doesn't exist or was never started (best-effort semantics per v1.7.21 spec).
func (*Instance) Substate ¶ added in v1.9.66
Substate returns the additive Honest-Status-v2 refinement for this session (see Substate). It reads the live tmux pane and classifies it; SubstateNone when there is no tmux session, the pane is dead, or the tool has no substate heuristics. This is an enrichment of Status, not a replacement — it never changes the canonical status reported by GetStatus/UpdateStatus.
func (*Instance) SupportsMCPAgentRestart ¶ added in v1.9.47
SupportsMCPAgentRestart is true when attach/detach --restart may reload the running agent.
func (*Instance) SyncSessionIDsFromTmux ¶ added in v0.26.0
func (i *Instance) SyncSessionIDsFromTmux()
SyncSessionIDsFromTmux reads tool session IDs from the tmux environment into the Instance struct. This is the reverse of SyncSessionIDsToTmux. Used in the stop path to capture IDs that may not have been saved during start (e.g., if PostStartSync timed out but the tool started late). Only updates fields where the tmux env has a non-empty value; does not blank existing IDs if the tmux env is missing the variable.
func (*Instance) SyncSessionIDsToTmux ¶ added in v0.8.95
func (i *Instance) SyncSessionIDsToTmux()
SyncSessionIDsToTmux syncs session IDs from Instance to tmux environment. PERFORMANCE: This is called on-demand (e.g., first attach) rather than at load time to reduce subprocess overhead during TUI startup.
Session IDs are needed in tmux environment for restart/resume operations that spawn new processes. Without this sync, R key wouldn't resume the correct session.
func (*Instance) SyncTmuxDisplayName ¶ added in v0.8.80
func (i *Instance) SyncTmuxDisplayName()
SyncTmuxDisplayName updates tmux-rendered UI that reflects the current title.
func (*Instance) UnregisterMCPChild ¶ added in v1.9.9
UnregisterMCPChild removes a previously registered MCP child PID, e.g. when the child has been observed exiting cleanly.
func (*Instance) UpdateClaudeSession ¶ added in v0.4.1
UpdateClaudeSession updates the Claude session ID from tmux environment. The capture-resume pattern (used in Start/Fork/Restart) sets CLAUDE_SESSION_ID in the tmux environment, making this the single authoritative source.
No file scanning fallback - we rely on the consistent capture-resume pattern.
func (*Instance) UpdateCodexSession ¶ added in v0.8.76
UpdateCodexSession updates the Codex session ID. Primary source: tmux environment. Fallback: project-aware filesystem scan.
func (*Instance) UpdateGeminiSession ¶ added in v0.8.1
UpdateGeminiSession updates the Gemini session ID, YOLO mode, analytics, and latest prompt. Delegates to focused helpers for each concern.
func (*Instance) UpdateHookStatus ¶ added in v0.16.0
func (i *Instance) UpdateHookStatus(status *HookStatus)
UpdateHookStatus updates the instance's hook-based status fields. Called by StatusFileWatcher when a hook status file changes.
func (*Instance) UpdateOpenCodeSSEStatus ¶ added in v1.10.10
UpdateOpenCodeSSEStatus feeds an SSE-derived status ("running"/"waiting") into the instance for the SSE fast path in UpdateStatus (issue #1614).
func (*Instance) UpdateOpenCodeSession ¶ added in v1.5.1
func (i *Instance) UpdateOpenCodeSession()
UpdateOpenCodeSession refreshes the OpenCode session ID from OpenCode CLI state without stealing a different tab's session from the same project.
func (*Instance) UpdateStatus ¶
func (*Instance) WaitForClaudeSession ¶ added in v0.4.1
WaitForClaudeSession waits for the tmux environment variable to be set. The capture-resume pattern sets CLAUDE_SESSION_ID in tmux env, so we poll for that. Returns the detected session ID or empty string after timeout.
func (*Instance) WaitForClaudeSessionWithExclude ¶ added in v0.5.0
func (i *Instance) WaitForClaudeSessionWithExclude(maxWait time.Duration, excludeIDs map[string]bool) string
WaitForClaudeSessionWithExclude waits for the tmux environment variable to be set. The excludeIDs parameter is kept for API compatibility but not used since tmux env is authoritative and won't return duplicate IDs.
func (*Instance) WriteGlobalMCPConfig ¶ added in v1.9.47
WriteGlobalMCPConfig writes catalog MCPs to this instance's global MCP store.
func (*Instance) WriteLocalMCPConfig ¶ added in v1.9.47
WriteLocalMCPConfig writes catalog MCPs to this instance's project-local MCP file.
type InstanceData ¶
type InstanceData struct {
ID string `json:"id"`
Title string `json:"title"`
ProjectPath string `json:"project_path"`
GroupPath string `json:"group_path"`
Order int `json:"order"`
ParentSessionID string `json:"parent_session_id,omitempty"` // Links to parent session (sub-session support)
IsConductor bool `json:"is_conductor,omitempty"` // True if this session is a conductor orchestrator
NoTransitionNotify bool `json:"no_transition_notify,omitempty"` // Suppress transition event dispatch
TitleLocked bool `json:"title_locked,omitempty"` // #697: block Claude session-name sync into Title
AutoName bool `json:"auto_name,omitempty"` // marks Title as a machine-generated quick-session handle
AutoNameDescription string `json:"auto_name_description,omitempty"` // last captured Claude task description for an AutoName session
Command string `json:"command"`
Wrapper string `json:"wrapper,omitempty"`
Tool string `json:"tool"`
Status Status `json:"status"`
CreatedAt time.Time `json:"created_at"`
LastAccessedAt time.Time `json:"last_accessed_at,omitempty"`
ArchivedAt time.Time `json:"archived_at,omitempty"`
TmuxSession string `json:"tmux_session"`
// TmuxSocketName is the tmux -L selector captured at Instance creation
// (issue #687, v1.7.50). Empty for pre-v1.7.50 rows — those keep hitting
// the default server after upgrade.
TmuxSocketName string `json:"tmux_socket_name,omitempty"`
// Worktree support
WorktreePath string `json:"worktree_path,omitempty"`
WorktreeRepoRoot string `json:"worktree_repo_root,omitempty"`
WorktreeBranch string `json:"worktree_branch,omitempty"`
// Account is the per-session named account (issue #924). See
// Instance.Account for full semantics.
Account string `json:"account,omitempty"`
// Pin anchors the session to the top/bottom of its group (pin-sessions).
// Round-trips through the pin column. Empty = not pinned.
Pin PinMode `json:"pin,omitempty"`
// Claude session (persisted for resume after app restart)
ClaudeSessionID string `json:"claude_session_id,omitempty"`
ClaudeDetectedAt time.Time `json:"claude_detected_at,omitempty"`
// Gemini session (persisted for resume after app restart)
GeminiSessionID string `json:"gemini_session_id,omitempty"`
GeminiDetectedAt time.Time `json:"gemini_detected_at,omitempty"`
GeminiYoloMode *bool `json:"gemini_yolo_mode,omitempty"`
GeminiModel string `json:"gemini_model,omitempty"`
// OpenCode session (persisted for resume after app restart)
OpenCodeSessionID string `json:"opencode_session_id,omitempty"`
OpenCodeDetectedAt time.Time `json:"opencode_detected_at,omitempty"`
// Codex session (persisted for resume after app restart)
CodexSessionID string `json:"codex_session_id,omitempty"`
CodexDetectedAt time.Time `json:"codex_detected_at,omitempty"`
// Latest user input for context
LatestPrompt string `json:"latest_prompt,omitempty"`
Notes string `json:"notes,omitempty"`
// Tool-specific launch options (generic for all tools: claude, codex, etc.)
ToolOptionsJSON json.RawMessage `json:"tool_options,omitempty"`
// MCP tracking (persisted for sync status display)
LoadedMCPNames []string `json:"loaded_mcp_names,omitempty"`
// Plugin channels (persisted for --channels CLI flag on Claude restart)
Channels []string `json:"channels,omitempty"`
// Plugins is the catalog-key list of Claude Code plugins enabled for
// this session (RFC docs/rfc/PLUGIN_ATTACH.md). Resolved through
// [plugins.<name>] in ~/.agent-deck/config.toml at spawn time and
// emitted as enabledPlugins[<id>] = true in the per-session scratch
// settings.json by EnsureWorkerScratchConfigDir.
Plugins []string `json:"plugins,omitempty"`
// PluginChannelLinkDisabled mirrors Instance.PluginChannelLinkDisabled
// (RFC §4.7) for state.db round-trip.
PluginChannelLinkDisabled bool `json:"plugin_channel_link_disabled,omitempty"`
// AutoLinkedChannels mirrors Instance.AutoLinkedChannels (RFC §4.7,
// fixes G4/C2). Persisted so reconciliation can clean up channels
// auto-added in a previous session even after the user toggles
// PluginChannelLinkDisabled or removes the plugin from the catalog.
AutoLinkedChannels []string `json:"auto_linked_channels,omitempty"`
// User-supplied claude CLI tokens, appended to every start/resume/fork
// command. Persisted so restarts preserve custom flags like --agent/--model.
ExtraArgs []string `json:"extra_args,omitempty"`
// Color is an optional per-session TUI row tint (issue #391). Empty = no tint.
Color string `json:"color,omitempty"`
// Sandbox support
Sandbox *SandboxConfig `json:"sandbox,omitempty"`
SandboxContainer string `json:"sandbox_container,omitempty"`
// SSH remote support
SSHHost string `json:"ssh_host,omitempty"`
SSHRemotePath string `json:"ssh_remote_path,omitempty"`
// Multi-repo support
MultiRepoEnabled bool `json:"multi_repo_enabled,omitempty"`
AdditionalPaths []string `json:"additional_paths,omitempty"`
MultiRepoTempDir string `json:"multi_repo_temp_dir,omitempty"`
MultiRepoWorktrees []statedb.MultiRepoWorktreeData `json:"multi_repo_worktrees,omitempty"`
// IdleTimeoutSecs mirrors Instance.IdleTimeoutSecs (#1143). 0 = disabled.
IdleTimeoutSecs int64 `json:"idle_timeout_secs,omitempty"`
}
InstanceData represents the serializable session data
type InstanceSettings ¶ added in v0.8.74
type InstanceSettings struct {
// AllowMultiple allows running multiple agent-deck TUI instances for the same profile.
// When false (default), only one instance can run per profile — a safe default that
// prevents concurrent reviver/restart loops from tearing down each other's live
// sessions (issue #1246). When true (explicit opt-in), multiple instances can run,
// but only the first (primary) manages the notification bar — useful for multi-pane
// workflows (e.g. PC + phone-over-SSH).
AllowMultiple *bool `toml:"allow_multiple,omitempty"`
// FollowCwdOnAttach updates the session's ProjectPath from tmux pane_current_path
// after returning from attach, and persists the new path.
// Default: false
FollowCwdOnAttach *bool `toml:"follow_cwd_on_attach,omitempty"`
}
InstanceSettings configures multiple agent-deck instance behavior
func GetInstanceSettings ¶ added in v0.8.74
func GetInstanceSettings() InstanceSettings
GetInstanceSettings returns instance behavior settings
func (*InstanceSettings) GetAllowMultiple ¶ added in v0.8.97
func (i *InstanceSettings) GetAllowMultiple() bool
GetAllowMultiple returns whether multiple instances are allowed, defaulting to false. Single-instance-per-profile is the safe default: it engages the primary-election gate so a second instance is rejected, preventing concurrent reviver/restart loops from tearing down each other's live sessions (issue #1246). Multi-instance is an explicit opt-in via allow_multiple = true.
func (*InstanceSettings) GetFollowCwdOnAttach ¶ added in v0.21.0
func (i *InstanceSettings) GetFollowCwdOnAttach() bool
GetFollowCwdOnAttach returns whether attach-return CWD follow is enabled.
type Item ¶
type Item struct {
Type ItemType
Group *Group
Session *Instance
RemoteSession *RemoteSessionInfo // Set for ItemTypeRemoteSession/ItemTypeRemoteGroup
RemoteName string // Remote name for remote items
Level int // Indentation level (0 for root groups, 1 for sessions)
Path string // Group path for this item
IsLastInGroup bool // True if this is the last session in its group (for tree rendering)
RootGroupNum int // Pre-computed root group number for hotkey display (1-9, 0 if not a root group)
IsSubSession bool // True if this session has a parent session
IsLastSubSession bool // True if this is the last sub-session of its parent (for tree rendering)
ParentIsLastInGroup bool // True if parent session is last top-level item (for tree line rendering)
IsWindow bool // True for ItemTypeWindow items
IsLastWindow bool // True if last window of parent session
WindowIndex int // Tmux window index (for ItemTypeWindow)
WindowName string // Tmux window name (for ItemTypeWindow)
WindowSessionID string // Parent session ID (for ItemTypeWindow)
WindowTool string // Detected tool in this window (claude, gemini, etc.)
CreatingID string // Non-empty for placeholder items (worktree creation in progress)
CreatingTitle string // Display title for creating placeholder
CreatingTool string // Tool for creating placeholder
DividerLabel string // Label shown on an ItemTypeDivider row (e.g. "idle / done")
}
Item represents a single item in the flattened group tree view
func PartitionByViewMode ¶ added in v1.9.58
func PartitionByViewMode(items []Item, mode GroupViewMode, activity map[string]GroupActivity) []Item
PartitionByViewMode re-orders an already-flattened item list into a top section and a bottom section separated by an ItemTypeDivider row.
Session-row placement is derived from the flattened items (mirroring the post-flatten filtering the "!" status filter uses, so tree-connector rendering stays consistent). Group-header placement, however, is derived from `activity` — a collapse-agnostic per-group summary built from the full tree (see GroupTree.GroupActivityMap). This matters because a *collapsed* group contributes a header row but no session rows; without the tree view it would be misread as empty and sink to the bottom even when it holds running work.
`activity` may be nil (e.g. in pure tests over fully-expanded lists): then header placement falls back to the visible session rows, and groups with no visible rows are treated as empty.
If the mode is GroupViewNormal, or if either section would be empty, the original slice is returned unchanged (no divider).
func (Item) IsCreatingPlaceholder ¶ added in v1.10.10
IsCreatingPlaceholder reports whether this row is a still-creating session placeholder: a session-typed row whose *Instance has not been created yet (Session == nil). Mutations (move/rename/fork) must be refused on such rows — dereferencing the nil Instance panics (#1540). This is the single model-layer predicate that generalizes the per-call-site nil guards.
type LocalMCP ¶ added in v0.6.1
type LocalMCP struct {
Name string // MCP name
SourcePath string // Directory containing the .mcp.json file
}
LocalMCP represents an MCP defined in a local .mcp.json file
type LogSettings ¶ added in v0.5.3
type LogSettings struct {
// MaxSizeMB is the maximum size in MB before a log file is truncated
// When a log exceeds this size, it keeps only the last MaxLines lines
// Default: 10 (10MB)
MaxSizeMB int `toml:"max_size_mb,omitzero"`
// MaxLines is the number of lines to keep when truncating
// Default: 10000
MaxLines int `toml:"max_lines,omitzero"`
// RemoveOrphans removes log files for sessions that no longer exist
// Default: true (nil = true)
RemoveOrphans *bool `toml:"remove_orphans,omitempty"`
// DebugLevel sets the minimum log level: "debug", "info", "warn", "error"
// Default: "info"
DebugLevel string `toml:"debug_level,omitempty"`
// DebugFormat sets the log format: "json" (default) or "text"
DebugFormat string `toml:"debug_format,omitempty"`
// DebugMaxMB is the max size in MB for debug.log before rotation
// Default: 10
DebugMaxMB int `toml:"debug_max_mb,omitzero"`
// DebugBackups is the number of rotated debug.log files to keep
// Default: 5
DebugBackups int `toml:"debug_backups,omitzero"`
// DebugRetentionDays is the number of days to keep rotated debug logs
// Default: 10
DebugRetentionDays int `toml:"debug_retention_days,omitzero"`
// DebugCompress enables gzip compression for rotated debug logs
// Default: true
DebugCompress *bool `toml:"debug_compress,omitempty"`
// RingBufferMB is the in-memory ring buffer size in MB for crash dumps
// Default: 10
RingBufferMB int `toml:"ring_buffer_mb,omitzero"`
// PprofEnabled starts a pprof server on localhost:6060 when debug mode is active
// Default: false
PprofEnabled bool `toml:"pprof_enabled,omitempty"`
// AggregateIntervalS is the event aggregation flush interval in seconds
// Default: 30
AggregateIntervalS int `toml:"aggregate_interval_secs,omitzero"`
}
LogSettings defines log file management configuration
func GetLogSettings ¶ added in v0.5.3
func GetLogSettings() LogSettings
GetLogSettings returns log management settings with defaults applied
func (LogSettings) GetDebugCompress ¶ added in v1.9.61
func (l LogSettings) GetDebugCompress() bool
func (LogSettings) GetRemoveOrphans ¶ added in v1.9.61
func (l LogSettings) GetRemoveOrphans() bool
GetRemoveOrphans returns whether orphan log removal is enabled (default: true).
type MCPDef ¶ added in v0.5.3
type MCPDef struct {
// Command is the executable to run (e.g., "npx", "docker", "node")
// Required for stdio MCPs, optional for HTTP/SSE MCPs
Command string `toml:"command,omitempty"`
// Args are command-line arguments
Args []string `toml:"args,omitempty"`
// Env is optional environment variables
Env map[string]string `toml:"env,omitempty"`
// Description is optional help text shown in the MCP Manager
Description string `toml:"description,omitempty"`
// URL is the endpoint for HTTP/SSE MCPs (e.g., "http://localhost:8000/mcp")
// If set, this MCP uses HTTP or SSE transport instead of stdio
URL string `toml:"url,omitempty"`
// Transport specifies the MCP transport type: "stdio" (default), "http", or "sse"
// Only needed when URL is set; defaults to "http" if URL is present
Transport string `toml:"transport,omitempty"`
// Headers is optional HTTP headers for HTTP/SSE MCPs (e.g., for authentication)
// Example: { Authorization = "Bearer token123" }
Headers map[string]string `toml:"headers,omitempty"`
// Server defines how to auto-start an HTTP MCP server process
// When set, agent-deck will start the server before connecting via HTTP
// This is optional - you can also connect to externally managed servers
Server *HTTPServerConfig `toml:"server,omitempty"`
}
MCPDef defines an MCP server configuration for the MCP Manager
func GetMCPDef ¶ added in v0.5.3
GetMCPDef returns a specific MCP definition by name Returns nil if not found
func (*MCPDef) GetTransport ¶ added in v0.8.96
GetTransport returns the transport type, defaulting to "http" if URL is set
func (*MCPDef) HasAutoStartServer ¶ added in v0.8.96
HasAutoStartServer returns true if this HTTP MCP has server auto-start configured
type MCPInfo ¶ added in v0.5.3
type MCPInfo struct {
Global []string // From CLAUDE_CONFIG_DIR/.claude.json mcpServers
Project []string // From CLAUDE_CONFIG_DIR/.claude.json projects[path].mcpServers
LocalMCPs []LocalMCP // From .mcp.json files (walks up parent directories)
}
MCPInfo contains MCP server information for a session
func GetCodexMCPInfo ¶ added in v1.10.9
GetCodexMCPInfo reads Codex MCP config from $CODEX_HOME/config.toml.
func GetCursorMCPInfo ¶ added in v1.9.47
GetCursorMCPInfo reads Cursor Agent CLI MCP config: ~/.cursor/mcp.json (global) and <project>/.cursor/mcp.json (project). Matches Cursor docs merge semantics for display.
func GetGeminiMCPInfo ¶ added in v0.8.1
GetGeminiMCPInfo reads MCP configuration from settings.json Returns MCPInfo with Global MCPs only (Gemini has no project-level MCPs) VERIFIED: settings.json structure is simple {"mcpServers": {...}}
func GetMCPInfo ¶ added in v0.5.3
GetMCPInfo retrieves MCP server information for a project path (cached) It reads from three sources: 1. Global MCPs: CLAUDE_CONFIG_DIR/.claude.json → mcpServers 2. Project MCPs: CLAUDE_CONFIG_DIR/.claude.json → projects[projectPath].mcpServers 3. Local MCPs: {projectPath}/.mcp.json → mcpServers
func GetOpenCodeMCPInfo ¶ added in v1.9.59
GetOpenCodeMCPInfo reads OpenCode MCP config: ~/.config/opencode/opencode.json (global) and <project>/opencode.json (project-local). Returns merged MCPInfo for display.
func MCPInfoForLocalAttach ¶ added in v1.9.47
MCPInfoForLocalAttach returns MCP info used for CLI/TUI local attach and detach. Gemini is special: global MCPs live in settings.json, but "local" CLI scope still targets the Claude-style .mcp.json walker in the project tree (legacy behavior).
func (*MCPInfo) AllNames ¶ added in v0.5.3
AllNames returns a deduplicated, sorted list of all MCP names across all sources Used for capturing loaded MCPs at session start for sync tracking
type MCPMode ¶ added in v0.5.3
type MCPMode int
MCPMode indicates how MCP enabling/disabling is configured
func GetMCPMode ¶ added in v0.5.3
GetMCPMode determines the MCP configuration mode for a project
type MCPPoolSettings ¶ added in v0.6.2
type MCPPoolSettings struct {
// Enabled enables HTTP pool mode (default: false)
Enabled bool `toml:"enabled,omitempty"`
// AutoStart starts pool when agent-deck launches (default: true)
AutoStart *bool `toml:"auto_start,omitempty"`
// PortStart is the first port in the pool range (default: 8001)
PortStart int `toml:"port_start,omitzero"`
// PortEnd is the last port in the pool range (default: 8050)
PortEnd int `toml:"port_end,omitzero"`
// StartOnDemand starts MCPs lazily on first attach (default: false)
StartOnDemand bool `toml:"start_on_demand,omitempty"`
// ShutdownOnExit stops HTTP servers when agent-deck quits (default: true)
ShutdownOnExit *bool `toml:"shutdown_on_exit,omitempty"`
// PoolMCPs is the list of MCPs to run in pool mode
// Empty = auto-detect common MCPs (memory, exa, firecrawl, etc.)
PoolMCPs []string `toml:"pool_mcps,omitempty"`
// FallbackStdio uses stdio for MCPs without socket support (default: true)
FallbackStdio *bool `toml:"fallback_to_stdio,omitempty"`
// ShowStatus shows pool status in TUI (default: true)
ShowStatus *bool `toml:"show_pool_status,omitempty"`
// PoolAll pools all MCPs by default (default: false)
PoolAll bool `toml:"pool_all,omitempty"`
// ExcludeMCPs excludes specific MCPs from pool when pool_all = true
ExcludeMCPs []string `toml:"exclude_mcps,omitempty"`
// SocketWaitTimeout is seconds to wait for socket to become ready (default: 5)
SocketWaitTimeout int `toml:"socket_wait_timeout,omitzero"`
}
MCPPoolSettings defines HTTP MCP pool configuration
func (MCPPoolSettings) GetAutoStart ¶ added in v1.9.61
func (p MCPPoolSettings) GetAutoStart() bool
func (MCPPoolSettings) GetFallbackStdio ¶ added in v1.9.61
func (p MCPPoolSettings) GetFallbackStdio() bool
func (MCPPoolSettings) GetShowStatus ¶ added in v1.9.61
func (p MCPPoolSettings) GetShowStatus() bool
func (MCPPoolSettings) GetShutdownOnExit ¶ added in v1.9.61
func (p MCPPoolSettings) GetShutdownOnExit() bool
type MCPServer ¶ added in v0.5.3
MCPServer represents an MCP with its enabled state
func GetLocalMCPState ¶ added in v0.5.3
GetLocalMCPState returns Local MCPs with their enabled state
type MCPServerConfig ¶ added in v0.5.3
type MCPServerConfig struct {
Type string `json:"type,omitempty"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
Env map[string]string `json:"env,omitempty"`
URL string `json:"url,omitempty"` // For HTTP transport
Headers map[string]string `json:"headers,omitempty"` // For HTTP transport (e.g., Authorization)
}
MCPServerConfig represents an MCP server configuration (Claude's format)
type MaintenanceResult ¶ added in v0.8.79
type MaintenanceResult struct {
PrunedLogs int
PrunedBackups int
ArchivedSessions int
OrphanContainers int
Duration time.Duration
}
MaintenanceResult holds the outcome of a maintenance run.
func RunMaintenance ¶ added in v0.8.79
func RunMaintenance(ctx context.Context) MaintenanceResult
RunMaintenance executes all maintenance tasks and returns the result.
type MaintenanceSettings ¶ added in v0.8.79
type MaintenanceSettings struct {
// Enabled enables the maintenance worker (default: false)
// Prunes Gemini logs, cleans old backups, archives bloated sessions
Enabled bool `toml:"enabled,omitempty"`
}
MaintenanceSettings controls the automatic maintenance worker
func GetMaintenanceSettings ¶ added in v0.8.79
func GetMaintenanceSettings() MaintenanceSettings
GetMaintenanceSettings returns maintenance settings from config
type MatchRange ¶ added in v0.5.3
MatchRange represents a match position in content
type MaterializedProjectSkill ¶ added in v1.7.32
type MaterializedProjectSkill struct {
EntryName string `json:"entry_name"`
TargetPath string `json:"target_path"`
}
MaterializedProjectSkill is one on-disk skill entry under a managed project root.
func ListMaterializedProjectSkills ¶ added in v0.19.1
func ListMaterializedProjectSkills(projectPath string) ([]MaterializedProjectSkill, error)
ListMaterializedProjectSkills returns all entries currently present in known managed project roots.
type MigrationResult ¶ added in v0.3.0
type MigrationResult struct {
Migrated bool // True if migration was performed
ProfilePath string // Path to the migrated profile
SessionCount int // Number of sessions migrated
Message string // Human-readable message
}
MigrationResult contains information about the migration outcome
func MigrateToProfiles ¶ added in v0.3.0
func MigrateToProfiles() (*MigrationResult, error)
MigrateToProfiles migrates from the old single-file layout to the profiles layout. This is safe to call multiple times - it will only migrate once.
Old layout:
~/.agent-deck/sessions.json ~/.agent-deck/sessions.json.bak ~/.agent-deck/sessions.json.bak.1 ~/.agent-deck/sessions.json.bak.2
New layout:
~/.agent-deck/config.json ~/.agent-deck/profiles/default/sessions.json ~/.agent-deck/profiles/default/sessions.json.bak ~/.agent-deck/profiles/default/sessions.json.bak.1 ~/.agent-deck/profiles/default/sessions.json.bak.2 ~/.agent-deck/logs/ (unchanged)
type ModelInfo ¶ added in v1.9.14
ModelInfo is the normalized status view of a per-session model override. ModelID is the exact string passed to the underlying tool; Model and Version are best-effort display fields derived from common provider ID formats.
func ParseModelID ¶ added in v1.9.14
ParseModelID derives display-friendly model and version fields from common provider model IDs while preserving the exact ID for command execution.
type ModelPricing ¶ added in v0.8.27
ModelPricing holds pricing per million tokens for a model
type MultiRepoWorktree ¶ added in v0.26.2
type MultiRepoWorktree struct {
OriginalPath string `json:"original_path"`
WorktreePath string `json:"worktree_path"`
RepoRoot string `json:"repo_root"`
Branch string `json:"branch"`
}
MultiRepoWorktree tracks a worktree created for one repo in a multi-repo session.
type MultiRepoWorktreeResult ¶ added in v1.9.25
type MultiRepoWorktreeResult struct {
MappedPaths []string
Worktrees []MultiRepoWorktree
Warnings []string
}
func CreateMultiRepoWorktrees ¶ added in v1.9.25
type MutationError ¶ added in v1.7.70
func (*MutationError) Error ¶ added in v1.7.70
func (e *MutationError) Error() string
type NotificationEntry ¶ added in v0.8.43
type NotificationEntry struct {
SessionID string
TmuxName string
Title string
AssignedKey string
WaitingSince time.Time
Status Status // For icon rendering when show_all enabled
}
NotificationEntry represents a waiting session in the notification bar
type NotificationManager ¶ added in v0.8.43
type NotificationManager struct {
// contains filtered or unexported fields
}
NotificationManager tracks waiting sessions for the notification bar
func NewNotificationManager ¶ added in v0.8.43
func NewNotificationManager(maxShown int, showAll, minimal bool) *NotificationManager
NewNotificationManager creates a new notification manager
func (*NotificationManager) Add ¶ added in v0.8.43
func (nm *NotificationManager) Add(inst *Instance) error
Add registers a session as waiting (newest goes to position [0])
func (*NotificationManager) Clear ¶ added in v0.8.43
func (nm *NotificationManager) Clear()
Clear removes all notifications
func (*NotificationManager) Count ¶ added in v0.8.43
func (nm *NotificationManager) Count() int
Count returns number of notifications
func (*NotificationManager) FormatBar ¶ added in v0.8.43
func (nm *NotificationManager) FormatBar() string
FormatBar returns the formatted status bar text
func (*NotificationManager) GetEntries ¶ added in v0.8.43
func (nm *NotificationManager) GetEntries() []*NotificationEntry
GetEntries returns a copy of current entries (newest first)
func (*NotificationManager) GetSessionByKey ¶ added in v0.8.43
func (nm *NotificationManager) GetSessionByKey(key string) *NotificationEntry
GetSessionByKey returns the entry for a given key (1-6)
func (*NotificationManager) Has ¶ added in v0.8.43
func (nm *NotificationManager) Has(sessionID string) bool
Has checks if a session is in notifications
func (*NotificationManager) IsMinimal ¶ added in v0.19.15
func (nm *NotificationManager) IsMinimal() bool
IsMinimal reports whether this manager is in minimal (icon+count) mode. home.go uses this to skip key binding updates when minimal=true.
func (*NotificationManager) Remove ¶ added in v0.8.43
func (nm *NotificationManager) Remove(sessionID string)
Remove removes a session from notifications
func (*NotificationManager) SyncFromInstances ¶ added in v0.8.43
func (nm *NotificationManager) SyncFromInstances(instances []*Instance, currentSessionID string) (added, removed []string)
SyncFromInstances updates notifications based on current instance states Call this periodically to sync with actual session statuses
type NotificationsConfig ¶ added in v0.8.43
type NotificationsConfig struct {
// Enabled shows notification bar in tmux status (default: true, nil = true)
Enabled *bool `toml:"enabled,omitempty"`
// MaxShown is the maximum number of sessions shown in the bar (default: 6)
MaxShown int `toml:"max_shown,omitzero"`
// ShowAll displays all sessions (with status icons) instead of only waiting sessions (default: false)
ShowAll bool `toml:"show_all,omitempty"`
// Minimal shows a compact icon+count summary instead of session names: ● 2 │ ◐ 3 │ ○ 1
// When true, key bindings (Ctrl+b 1-6) are disabled. ShowAll is ignored. (default: false)
Minimal bool `toml:"minimal,omitempty"`
// TransitionEvents controls whether the transition daemon sends tmux messages
// to parent sessions when a child transitions (e.g., running → waiting).
// Default: true (nil = true). Set to false to suppress dispatch globally.
// Per-session override: Instance.NoTransitionNotify
TransitionEvents *bool `toml:"transition_events,omitempty"`
}
NotificationsConfig configures the waiting session notification bar
func GetNotificationsSettings ¶ added in v0.8.43
func GetNotificationsSettings() NotificationsConfig
GetNotificationsSettings returns notification bar settings with defaults applied
func (NotificationsConfig) GetEnabled ¶ added in v1.9.61
func (n NotificationsConfig) GetEnabled() bool
GetEnabled returns whether notifications are enabled (default: true).
func (NotificationsConfig) GetTransitionEventsEnabled ¶ added in v1.7.35
func (n NotificationsConfig) GetTransitionEventsEnabled() bool
GetTransitionEventsEnabled returns whether transition event dispatch is enabled. Defaults to true when unset (nil).
type OpenClawSettings ¶ added in v0.21.0
type OpenClawSettings struct {
// GatewayURL is the WebSocket URL of the OpenClaw gateway (default: "ws://127.0.0.1:31337")
GatewayURL string `toml:"gateway_url,omitempty"`
// Password is the gateway authentication password.
// Supports env var references (e.g. "$OPENCLAW_PASSWORD" or "${OPENCLAW_PASSWORD}").
// Falls back to OPENCLAW_PASSWORD env var if not set.
Password string `toml:"password,omitempty"`
// AutoSync syncs OpenClaw agents as agent-deck sessions on TUI startup
AutoSync bool `toml:"auto_sync,omitempty"`
// GroupName is the agent-deck group name for OpenClaw sessions (default: "openclaw")
GroupName string `toml:"group_name,omitempty"`
}
OpenClawSettings configures the OpenClaw gateway connection.
type OpenCodeOptions ¶ added in v0.12.1
type OpenCodeOptions struct {
// SessionMode: "new" (default), "continue" (-c), or "resume" (-s)
SessionMode string `json:"session_mode,omitempty"`
// ResumeSessionID is the session ID for -s flag (only when SessionMode="resume")
ResumeSessionID string `json:"resume_session_id,omitempty"`
// Model overrides the model (e.g., "anthropic/claude-sonnet-4-5-20250929")
Model string `json:"model,omitempty"`
// Agent overrides the agent to use
Agent string `json:"agent,omitempty"`
}
OpenCodeOptions holds launch options for OpenCode CLI sessions
func NewOpenCodeOptions ¶ added in v0.12.1
func NewOpenCodeOptions(config *UserConfig) *OpenCodeOptions
NewOpenCodeOptions creates OpenCodeOptions with defaults from config
func UnmarshalOpenCodeOptions ¶ added in v0.12.1
func UnmarshalOpenCodeOptions(data json.RawMessage) (*OpenCodeOptions, error)
UnmarshalOpenCodeOptions deserializes OpenCodeOptions from JSON wrapper
func (*OpenCodeOptions) ToArgs ¶ added in v0.12.1
func (o *OpenCodeOptions) ToArgs() []string
ToArgs returns command-line arguments based on options
func (*OpenCodeOptions) ToArgsForFork ¶ added in v0.12.1
func (o *OpenCodeOptions) ToArgsForFork() []string
ToArgsForFork returns arguments suitable for fork resume command. Fork uses -s internally, so session mode flags are excluded.
func (*OpenCodeOptions) ToolName ¶ added in v0.12.1
func (o *OpenCodeOptions) ToolName() string
ToolName returns "opencode"
type OpenCodeSSEStatus ¶ added in v1.10.10
type OpenCodeSSEStatus struct {
Status string // "running" or "waiting"
UpdatedAt time.Time // last time the stream confirmed this status
}
OpenCodeSSEStatus is the latest status derived from an instance's event stream.
type OpenCodeSSEWatcher ¶ added in v1.10.10
type OpenCodeSSEWatcher struct {
// contains filtered or unexported fields
}
OpenCodeSSEWatcher manages SSE connections to OpenCode instances.
func NewOpenCodeSSEWatcher ¶ added in v1.10.10
func NewOpenCodeSSEWatcher(onChange func()) *OpenCodeSSEWatcher
NewOpenCodeSSEWatcher creates a watcher. onChange (may be nil) is invoked whenever a derived status changes.
func (*OpenCodeSSEWatcher) GetStatus ¶ added in v1.10.10
func (w *OpenCodeSSEWatcher) GetStatus(instanceID string) *OpenCodeSSEStatus
GetStatus returns the latest derived status for an instance, or nil.
func (*OpenCodeSSEWatcher) Stop ¶ added in v1.10.10
func (w *OpenCodeSSEWatcher) Stop()
Stop tears down all connections.
func (*OpenCodeSSEWatcher) Sync ¶ added in v1.10.10
func (w *OpenCodeSSEWatcher) Sync(targets []SSETarget)
Sync reconciles active connections against the desired target set: it opens connections for new targets (or targets whose port changed after a restart) and tears down connections for instances no longer present.
type OpenCodeSettings ¶ added in v0.12.1
type OpenCodeSettings struct {
// DefaultModel is the model to use for new OpenCode sessions
// Format: "provider/model" (e.g., "anthropic/claude-sonnet-4-5-20250929")
// If empty, OpenCode uses its own default
DefaultModel string `toml:"default_model,omitempty"`
// DefaultAgent is the agent to use for new OpenCode sessions
// If empty, OpenCode uses its own default
DefaultAgent string `toml:"default_agent,omitempty"`
// EnvFile is a .env file specific to OpenCode sessions
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file,omitempty"`
// Command overrides the default binary/invocation for OpenCode sessions.
// Supports flags (e.g., "opencode --custom-flag"). Default: "opencode"
Command string `toml:"command,omitempty"`
// DisableSSEStatus turns off SSE-based status tracking (issue #1614).
// By default agent-deck launches OpenCode with an explicit --port so its
// /event SSE stream can drive real-time status (green while busy, yellow
// when waiting) instead of tmux content sniffing. Set true if your
// OpenCode version predates the top-level --port flag or you don't want
// a localhost event server bound per session.
DisableSSEStatus bool `toml:"disable_sse_status,omitempty"`
}
OpenCodeSettings defines OpenCode CLI configuration
type PathCandidate ¶ added in v1.10.10
type PathCandidate struct {
Path string
Source PathSource
LastAccess time.Time // zero when unknown (group/zoxide)
Score float64
}
PathCandidate is a single ranked suggestion for a new session's project path.
func PathSuggest ¶ added in v1.10.10
func PathSuggest(in PathSuggestInput) []PathCandidate
PathSuggest merges the candidate sources, frecency-ranks them, dedups by normalized path (keeping the strongest source), applies the query filter and returns at most Limit candidates, best-first.
type PathSource ¶ added in v1.10.10
type PathSource string
PathSource labels where a path candidate originated. It drives both the display hint in the picker and the base weight used for ranking.
const ( // SourceRecent is a path that backs an existing (or past) session. SourceRecent PathSource = "recent" // SourceGroup is a group's configured/derived default path. SourceGroup PathSource = "group" // SourceZoxide is a hit from the zoxide frecency database. SourceZoxide PathSource = "zoxide" // SourceLiteral is a path the user typed verbatim (added by the caller). SourceLiteral PathSource = "literal" )
type PathSuggestInput ¶ added in v1.10.10
type PathSuggestInput struct {
Instances []*Instance // recents source (project paths + access times)
GroupDefaults []string // resolved DefaultPathForGroup values
Zoxide []string // zoxide results, best-first
Query string // case-insensitive substring filter (optional)
Now time.Time // reference time for recency decay; zero → now
Limit int // max candidates; <=0 → defaultPathSuggestLimit
}
PathSuggestInput carries the already-collected raw inputs. All I/O (the zoxide query, filesystem probing) is performed by the caller so PathSuggest stays pure and deterministically testable — the single ranking used by the TUI picker, the CLI and the ADE endpoints.
type PerformanceSettings ¶ added in v1.10.9
type PerformanceSettings struct {
// ClaimPolling enables per-session ownership claims in state.db: each
// session is actively polled by exactly one instance; others render its
// status from the DB. Default false (every instance polls everything,
// today's behavior).
//
// [performance]
// claim_polling = true
ClaimPolling *bool `toml:"claim_polling,omitempty"`
}
PerformanceSettings tunes background-work sharing between concurrent agent-deck instances.
type PinMode ¶ added in v1.9.56
type PinMode string
PinMode anchors a session to a fixed slot within its group, exempt from the status/recency actionable sort (pin-sessions feature). The empty value is the default so existing rows migrate cleanly through the `pin` column default.
type PluginDef ¶ added in v1.9.2
type PluginDef struct {
// Name is the short plugin name as exposed by the upstream marketplace's
// plugin.json (e.g. "telegram", "octopus"). Required.
Name string `toml:"name,omitempty"`
// Source is the marketplace identifier the plugin lives in. Either a
// curated marketplace name (e.g. "claude-plugins-official") or a github
// "owner/repo" pair (e.g. "nyldn/claude-octopus"). Required.
Source string `toml:"source,omitempty"`
// EmitsChannel hints that this plugin participates in the inbound
// `notifications/claude/channel` protocol — when true, attaching the
// plugin via --plugin auto-populates Instance.Channels with
// "plugin:<Name>@<Source>" so the harness registers the inbound handler.
// Catalog hint only; agent-deck does not introspect the plugin source.
EmitsChannel bool `toml:"emits_channel,omitempty"`
// AutoInstall enables shell-out to `claude plugin install <Name>@<Source>`
// at session spawn when the plugin code is not yet present under the
// source profile's plugins/ directory. Best-effort: install failure is
// logged but does not block session start.
AutoInstall bool `toml:"auto_install,omitempty"`
// Description is optional help text shown in the Edit Session dialog
// pill list.
Description string `toml:"description,omitempty"`
}
PluginDef defines a Claude Code plugin entry exposed via `agent-deck add --plugin <name>` and `agent-deck session set <id> plugins <csv>`.
Plugin id at runtime is constructed as "<Name>@<Source>" and written to the per-session scratch settings.json under enabledPlugins (see internal/session/worker_scratch.go). v1 is catalog-only: only short names listed in [plugins.<name>] tables in ~/.agent-deck/config.toml are valid values for the --plugin flag.
RFC: docs/rfc/PLUGIN_ATTACH.md.
func GetPluginDef ¶ added in v1.9.2
GetPluginDef returns a specific plugin definition by catalog key. Returns nil if not found OR if the entry matches the v1 refusal policy.
type PreviewSettings ¶ added in v0.8.27
type PreviewSettings struct {
// ShowOutput shows terminal output in preview pane (including launch animation)
// Default: true (pointer to distinguish "not set" from "explicitly false")
ShowOutput *bool `toml:"show_output,omitempty"`
// ShowAnalytics shows session analytics panel for Claude sessions
// Default: false (pointer to distinguish "not set" from "explicitly false")
ShowAnalytics *bool `toml:"show_analytics,omitempty"`
// ShowNotes shows session notes section in preview pane
// Default: false (pointer to distinguish "not set" from "explicitly true")
ShowNotes *bool `toml:"show_notes,omitempty"`
// Analytics configures which sections to show in the analytics panel
Analytics AnalyticsDisplaySettings `toml:"analytics,omitempty"`
// NotesOutputSplit controls vertical space allocation between notes and output
// in the preview pane when output is visible.
// Range: 0.1 - 0.9 (fraction reserved for notes). Default: 0.33
NotesOutputSplit float64 `toml:"notes_output_split,omitzero"`
}
PreviewSettings defines preview pane configuration
func GetPreviewSettings ¶ added in v0.8.27
func GetPreviewSettings() PreviewSettings
GetPreviewSettings returns preview settings with defaults applied
func (*PreviewSettings) GetAnalyticsSettings ¶ added in v0.8.53
func (p *PreviewSettings) GetAnalyticsSettings() AnalyticsDisplaySettings
GetAnalyticsSettings returns the analytics display settings with defaults applied
func (*PreviewSettings) GetNotesOutputSplit ¶ added in v0.21.0
func (p *PreviewSettings) GetNotesOutputSplit() float64
GetNotesOutputSplit returns notes/output split ratio, clamped to sane bounds.
func (*PreviewSettings) GetShowAnalytics ¶ added in v0.8.27
func (p *PreviewSettings) GetShowAnalytics() bool
GetShowAnalytics returns whether to show analytics, defaulting to false
func (*PreviewSettings) GetShowNotes ¶ added in v0.25.0
func (p *PreviewSettings) GetShowNotes() bool
GetShowNotes returns whether to show notes section, defaulting to false
func (*PreviewSettings) GetShowOutput ¶ added in v0.8.33
func (p *PreviewSettings) GetShowOutput() bool
GetShowOutput returns whether to show terminal output, defaulting to true
type PricingOverride ¶ added in v0.26.4
type PricingSettings ¶ added in v0.26.4
type PricingSettings struct {
Overrides map[string]PricingOverride `toml:"overrides,omitempty"`
}
type ProfileClaudeSettings ¶ added in v0.19.1
type ProfileClaudeSettings struct {
// ConfigDir overrides [claude].config_dir for this profile only.
ConfigDir string `toml:"config_dir,omitempty"`
}
ProfileClaudeSettings defines profile-specific Claude overrides.
type ProfileCodexSettings ¶ added in v1.9.18
type ProfileCodexSettings struct {
// ConfigDir overrides [codex].config_dir for this profile only.
ConfigDir string `toml:"config_dir,omitempty"`
}
ProfileCodexSettings defines profile-specific Codex overrides.
type ProfileCosts ¶ added in v1.7.75
type ProfileCosts struct {
CostLineTemplate *string `toml:"cost_line_template,omitempty"`
CostLineHideWhenZero *bool `toml:"cost_line_hide_when_zero,omitempty"`
}
ProfileCosts holds per-profile overrides for cost-related settings. Pointer fields use the same fall-through semantics as CostsSettings.
type ProfileMigrateOptions ¶ added in v1.9.2
type ProfileMigrateOptions struct {
// Force bypasses the "session is running" safety check. Without this, a
// running session is refused with a hint to stop it first.
Force bool
}
ProfileMigrateOptions controls a cross-profile migration (issue #928).
type ProfileMigrateResult ¶ added in v1.9.2
type ProfileMigrateResult struct {
// MovedSessionIDs are the IDs that ended up in the destination DB. This
// includes IDs that were already there (idempotent re-run).
MovedSessionIDs []string
// SkippedIdempotent lists session IDs that were already in the destination
// (the migration only needed to clean up the source side, if any).
SkippedIdempotent []string
// MovedCostEvents is the count of cost_events rows inserted at dst.
MovedCostEvents int
// MovedWatcherEvents is the count of watcher_events rows inserted at dst.
MovedWatcherEvents int
// CreatedGroups lists group_path values that did not exist in dst and were
// created by the migration.
CreatedGroups []string
// MetaUpdated indicates the conductor meta.json was rewritten with the new
// profile (conductor migrations only).
MetaUpdated bool
}
ProfileMigrateResult summarizes what a migration moved. All counts include rows that were already present at the destination (skipped idempotently).
func MigrateConductorToProfile ¶ added in v1.9.2
func MigrateConductorToProfile(name, sourceProfile, targetProfile string, opts ProfileMigrateOptions) (*ProfileMigrateResult, error)
MigrateConductorToProfile moves a conductor session AND every child session (where parent_session_id == conductor.ID) from src to dst, then atomically rewrites ~/.agent-deck/conductor/<name>/meta.json with the new profile.
Per CLAUDE.md and issue #928: workers MUST travel with their conductor — a split would orphan the children's parent_session_id reference.
func MigrateGroupToProfile ¶ added in v1.9.2
func MigrateGroupToProfile(groupPath, sourceProfile, targetProfile string, opts ProfileMigrateOptions) (*ProfileMigrateResult, error)
MigrateGroupToProfile moves every session whose group_path matches groupPath from src to dst. The group row itself (preserving expanded/order/default_path) is also created in dst. Empty groups are refused.
func MigrateSessionsToProfile ¶ added in v1.9.2
func MigrateSessionsToProfile(sourceProfile, targetProfile string, sessionIDs []string, opts ProfileMigrateOptions) (*ProfileMigrateResult, error)
MigrateSessionsToProfile moves the listed session rows from sourceProfile to targetProfile. All associated rows (cost_events, watcher_events linked via session_id or triage_session_id) are moved alongside. The session's group row is created in the target if missing.
The algorithm is target-write-then-source-delete, with a best-effort rollback on source-delete failure. Two SQLite databases cannot share a transaction, so we accept a tiny window where a crash between phases would leave the row in both DBs — re-running the command cleans this up.
Refuses running sessions unless opts.Force is set. Idempotent: a session already in dst is treated as already-migrated and only the source side is reconciled.
type ProfileSettings ¶ added in v0.19.1
type ProfileSettings struct {
// Claude defines Claude Code overrides for a specific profile.
Claude ProfileClaudeSettings `toml:"claude,omitempty"`
// Codex defines Codex CLI overrides for a specific profile.
Codex ProfileCodexSettings `toml:"codex,omitempty"`
// Costs defines profile-specific cost-tracking overrides.
// Nil pointer means "no [profiles.<name>.costs] block in TOML"; the
// resolver falls through to global [costs] settings.
Costs *ProfileCosts `toml:"costs,omitempty"`
}
ProfileSettings defines per-profile configuration overrides.
type ProjectMCPSettings ¶ added in v0.5.3
type ProjectMCPSettings struct {
EnableAllProjectMcpServers bool `json:"enableAllProjectMcpServers,omitempty"`
EnabledMcpjsonServers []string `json:"enabledMcpjsonServers,omitempty"`
DisabledMcpjsonServers []string `json:"disabledMcpjsonServers,omitempty"`
}
ProjectMCPSettings represents .claude/settings.local.json
type ProjectSkillAttachment ¶ added in v0.19.1
type ProjectSkillAttachment struct {
ID string `toml:"id" json:"id"`
Name string `toml:"name" json:"name"`
Source string `toml:"source" json:"source"`
SourcePath string `toml:"source_path" json:"source_path"`
EntryName string `toml:"entry_name" json:"entry_name"`
TargetPath string `toml:"target_path" json:"target_path"` // relative to project path
Mode string `toml:"mode,omitempty" json:"mode,omitempty"`
AttachedAt string `toml:"attached_at,omitempty" json:"attached_at,omitempty"`
}
ProjectSkillAttachment is persisted in .agent-deck/skills.toml.
The json tags are required for the web API: without them encoding/json ignores the toml tags and falls back to the exported field names, emitting PascalCase (`Name`) instead of the `name` the frontend + e2e tests read. Tag style mirrors the sibling SkillCandidate so both skill types serialize consistently across the /api/skills surface.
func AttachSkillToProject ¶ added in v0.19.1
func AttachSkillToProject(projectPath, tool, skillRef, sourceName string) (*ProjectSkillAttachment, error)
AttachSkillToProject resolves and attaches one skill into the runtime-specific project skills dir.
func DetachSkillFromProject ¶ added in v0.19.1
func DetachSkillFromProject(projectPath, skillRef, sourceName string) (*ProjectSkillAttachment, error)
DetachSkillFromProject detaches one managed skill and removes its manifest entry.
func GetAttachedProjectSkills ¶ added in v0.19.1
func GetAttachedProjectSkills(projectPath string) ([]ProjectSkillAttachment, error)
GetAttachedProjectSkills returns manifest-backed attached skills.
type ProjectSkillsManifest ¶ added in v0.19.1
type ProjectSkillsManifest struct {
Skills []ProjectSkillAttachment `toml:"skills"`
}
ProjectSkillsManifest is the project-local attachment state.
func LoadProjectSkillsManifest ¶ added in v0.19.1
func LoadProjectSkillsManifest(projectPath string) (*ProjectSkillsManifest, error)
LoadProjectSkillsManifest reads project attachment state.
type Registry ¶ added in v1.9.47
type Registry struct {
// contains filtered or unexported fields
}
Registry is the unified, in-memory view of every tool agent-deck knows about: the canonical built-ins (static, from builtins.go) plus any user-defined [tools.<name>] entries merged in at Init time.
It replaces the two-source-of-truth split that issue #1258 describes:
- detectTool() -> Registry.Match()
- isBuiltinToolName() -> Registry.IsBuiltin()
- GetToolDef() -> Registry.GetCustom() (see note below)
- GetCustomToolNames() -> Registry.CustomNames()
Precedence rule (issue #1258 — chose option (a): reject + warn):
A custom [tools.<name>] whose name EXACTLY matches a built-in is rejected with a startup warning, and the built-in is kept. This preserves the prior "built-in wins" behavior while making the previously-silent shadow (#13) explicit. To customize a built-in's command, use a different name plus compatible_with = "<builtin>".
A Registry is immutable after Init; rebuild via Init for a new config.
func Init ¶ added in v1.9.47
Init builds a Registry from the static built-ins plus the supplied custom tools (typically config.Tools from LoadUserConfig). Custom entries whose name shadows a built-in are dropped with a warning (precedence rule (a)).
Init is the single explicit constructor — tests build registries directly via Init(map[string]ToolDef{...}) rather than poking package globals. It builds an UNFILTERED registry (show_only_installed_tools off): no PATH probing happens, so behavior is byte-identical to before issue #1259.
func InitFiltered ¶ added in v1.9.48
func InitFiltered(custom map[string]ToolDef, showOnlyInstalled bool, hiddenTools []string) *Registry
InitFiltered builds a Registry, optionally running the show_only_installed_tools probe (issue #1259). When showOnlyInstalled is false the probe is skipped ENTIRELY — not "probe then ignore" — so the default path makes zero LookPath calls and is byte-identical to Init's prior behavior.
Caching policy: the probe runs only here, at construction time. The process registry (currentRegistry) rebuilds whenever the cached *UserConfig pointer changes, so a mid-session config edit re-probes on the next dialog open. There is deliberately no separate timer/refresh path (issue #1259 caching note).
func (*Registry) All ¶ added in v1.9.47
All returns every canonical built-in as a ToolDef, in precedence order. This is the registry-as-data answer to "what are the built-ins?" (the old isBuiltinToolName set). Custom tools are reached via CustomNames()/Get().
func (*Registry) CustomNames ¶ added in v1.9.47
CustomNames returns the sorted names of user-defined tools (built-in shadows already excluded at Init). Returns nil when there are no custom tools, exactly like the legacy GetCustomToolNames(). Replaces GetCustomToolNames()'s body.
func (*Registry) FilterActive ¶ added in v1.9.48
FilterActive reports whether the show_only_installed_tools filter is on, regardless of whether the empty-fallback engaged.
func (*Registry) FilterFallback ¶ added in v1.9.48
FilterFallback reports the empty-fallback state: the filter is on but nothing other than shell resolved on PATH, so the dialogs show the full list plus a one-line hint instead of trapping the user with an empty selection.
func (*Registry) FilterVisibleNames ¶ added in v1.9.48
FilterVisibleNames returns names with hidden tools removed. The empty command "" (shell) is always kept.
func (*Registry) Get ¶ added in v1.9.47
Get returns the unified entry for name as a *ToolDef: a custom tool if one is defined, otherwise a synthesized ToolDef for the built-in, otherwise nil.
This is the new single lookup surface for NEW code that genuinely wants "tell me about this tool, built-in or custom." Existing callers stay on GetCustom via GetToolDef() to remain byte-identical (see GetCustom).
func (*Registry) GetCustom ¶ added in v1.9.47
GetCustom returns the user-defined ToolDef for name, or nil if name is not a custom tool. Built-in names return nil here (a shadowing custom entry was already rejected at Init), which is exactly the legacy GetToolDef() contract that many callers rely on (they branch on a nil result to fall back to built-in handling). GetToolDef() delegates here, NOT to Get().
func (*Registry) HiddenToolNames ¶ added in v1.9.54
HiddenToolNames returns the configured [ui].hidden_tools denylist.
func (*Registry) IsBuiltin ¶ added in v1.9.47
IsBuiltin reports whether name is one of the canonical built-in tools. Replaces isBuiltinToolName().
func (*Registry) IsVisible ¶ added in v1.9.48
IsVisible reports whether a tool name should appear in the new-session dialogs. The [ui].hidden_tools denylist is always applied. show_only_installed_tools is applied when enabled unless the empty-fallback is active. "shell" (and its empty-command alias "") is always visible.
func (*Registry) Match ¶ added in v1.9.47
Match resolves a command string to a tool name. Replaces detectTool().
Resolution order, preserving the legacy semantics exactly:
- Exact custom-tool name match on the RAW command (pre-lowercase), mirroring detectTool's leading `GetToolDef(cmd) != nil` check.
- Built-in detect patterns in canonical order (substring or token match, case-insensitive).
- Fallback to "shell".
type RemoteConfig ¶ added in v0.20.0
type RemoteConfig struct {
// Host is the SSH destination (e.g., "user@host" or "user@host:port")
Host string `toml:"host,omitempty"`
// AgentDeckPath is the path to agent-deck binary on the remote (default: "agent-deck")
AgentDeckPath string `toml:"agent_deck_path,omitempty"`
// Profile is the remote profile to use (default: "default")
Profile string `toml:"profile,omitempty"`
}
RemoteConfig defines a remote agent-deck instance accessible via SSH.
func (RemoteConfig) GetAgentDeckPath ¶ added in v0.20.0
func (rc RemoteConfig) GetAgentDeckPath() string
GetAgentDeckPath returns the agent-deck binary path, defaulting to "agent-deck".
func (RemoteConfig) GetProfile ¶ added in v0.20.0
func (rc RemoteConfig) GetProfile() string
GetProfile returns the remote profile, defaulting to "default".
type RemoteKeySender ¶ added in v1.9.24
type RemoteKeySender struct {
// contains filtered or unexported fields
}
RemoteKeySender dispatches insert-mode keystrokes to a session living on a remote agent-deck instance over the existing SSHRunner RPC (#1102 bug 2: previously, insert mode silently no-op'd for remote sessions because dispatch only saw `*Instance`, not `RemoteSessionInfo`). Each Send shells to the remote `agent-deck session send-keys <id>` subcommand; ControlMaster keeps the SSH multiplex channel warm so calls don't pay the full TCP+SSH handshake every time.
Unlike the local path, this does NOT open a long-running subprocess — `ssh ... agent-deck session send-keys <id> -- <text>` is fork+exec per call. Acceptable because:
- Remote latency is dominated by network RTT, not local fork+exec.
- SSH ControlMaster (already set up in SSHRunner) amortizes the connection cost across all calls during an insert-mode session.
- Keeping a persistent `ssh ... -t` interactive session would require a custom on-remote shell protocol — out of scope for the bugfix.
The remote side's `session send-keys` handler uses the existing tmux SendKeys / SendNamedKey / SendEnter on the remote Instance, so the behavior reaching the pane is bit-identical to a local insert-mode send.
func NewRemoteKeySender ¶ added in v1.9.24
func NewRemoteKeySender(runner *SSHRunner, sessionID string, ctx context.Context) *RemoteKeySender
NewRemoteKeySender wires up a sender bound to a single remote session. The context governs every Send call's deadline; pass context.Background() to inherit SSHRunner.Run's default 10s timeout, or a derived context for the lifetime of the insert-mode session.
func (*RemoteKeySender) Close ¶ added in v1.9.24
func (r *RemoteKeySender) Close() error
Close marks the sender as closed. Subsequent Send calls fail with "closed". Idempotent so panic-recovery flows can safely double-close. Does NOT tear down the SSH ControlMaster — that's shared across the process and persists per ssh -o ControlPersist=600.
func (*RemoteKeySender) SendEnter ¶ added in v1.9.24
func (r *RemoteKeySender) SendEnter() error
SendEnter forwards a single Enter keystroke. Modeled as a discrete flag so the remote handler can preserve the existing tmux SendEnter semantics (with bracketed-paste flush delay; see internal/tmux/tmux.go:3984).
func (*RemoteKeySender) SendKeys ¶ added in v1.9.24
func (r *RemoteKeySender) SendKeys(text string) error
SendKeys forwards `text` as literal keystrokes to the remote session. Empty text is a no-op so the caller doesn't have to guard before flushing an empty rune buffer.
func (*RemoteKeySender) SendNamedKey ¶ added in v1.9.24
func (r *RemoteKeySender) SendNamedKey(key string) error
SendNamedKey forwards a tmux named key (BSpace/Up/Down/Left/Right/Tab/ BTab/C-c/C-d — the same set #1094 added to the local path).
type RemoteLatency ¶ added in v1.9.24
type RemoteLatency struct {
// MS is the round-trip time in milliseconds. Meaningful only when
// Offline is false.
MS int
// Offline is true when the most recent measurement attempt failed
// (network error, SSH dead, remote agent-deck binary missing, etc).
Offline bool
// MeasuredAt is when the sample was taken; zero value means never measured.
MeasuredAt time.Time
}
RemoteLatency is a live round-trip-time sample for a configured remote. Tracked per remote host (not per session) — multiple sessions on the same host share the same connection, so latency is a host-level metric. See issue #1103.
type RemoteSessionInfo ¶ added in v0.20.0
type RemoteSessionInfo struct {
ID string `json:"id"`
Title string `json:"title"`
Path string `json:"path"`
Group string `json:"group"`
Tool string `json:"tool"`
Status string `json:"status"`
CreatedAt string `json:"created_at"`
// Set locally, not from JSON
RemoteName string `json:"-"`
}
RemoteSessionInfo represents a session from a remote agent-deck instance.
type ResolvedForkPlan ¶ added in v1.9.50
ResolvedForkPlan is the effective set of structural fork toggles after applying [fork] config + parent context.
type ResponseOutput ¶ added in v0.7.0
type ResponseOutput struct {
Tool string `json:"tool"` // Tool type (claude, gemini, etc.)
Role string `json:"role"` // Always "assistant" for now
Content string `json:"content"` // The actual response text
Timestamp string `json:"timestamp,omitempty"` // When the response was generated (Claude only)
SessionID string `json:"session_id,omitempty"` // Claude session ID (if available)
}
ResponseOutput represents a parsed response from an agent session
type RevivalClass ¶ added in v1.7.8
type RevivalClass int
RevivalClass categorizes an Instance's recoverability at scan time.
- ClassAlive — tmux session exists AND our control pipe is alive. The session is healthy; no action needed.
- ClassErrored — tmux session exists but the control pipe is dead (or Status == StatusError). Likely cause: SSH logout killed our inherited pipe but the tmux server survived under its own user scope. Revivable.
- ClassDead — tmux session does not exist. Server gone (OOM, reboot, explicit tmux kill-session, or a systemd scope reap that took the whole server). NOT auto-revived; user must explicit `session restart` because we cannot distinguish intentional kill from crash.
const ( ClassAlive RevivalClass = iota ClassErrored ClassDead )
func (RevivalClass) String ¶ added in v1.7.8
func (c RevivalClass) String() string
type ReviveBreaker ¶ added in v1.10.9
type ReviveBreaker struct {
// contains filtered or unexported fields
}
ReviveBreaker bounds futile control-pipe reconnect storms on a wedged tmux server (#1579).
The failure mode it guards: when the tmux SERVER wedges (stops serving control pipes for existing sessions while `has-session` still answers yes), every 60s reviver sweep classifies the affected sessions ClassErrored and "successfully" reconnects their pipes — which die again immediately. Because pm.Connect returns nil each time, there is no failure signal, so the reviver respawns the same dead pipes forever (observed: 37 respawns of ~7 sessions in 9 minutes). Pressing R in the TUI just triggers another doomed attach. Only a full `pkill tmux` + relaunch recovers, and nothing tells the user that.
The breaker detects futility by transition, not by action return code: a revive is futile when the session is ClassErrored AGAIN on a later sweep despite a prior "successful" revive, or when the revive action itself errors. After FutilityThreshold consecutive futile revives it opens the session's circuit — revives are skipped and throttled to one probe per exponential cooldown (InitialCooldown → MaxCooldown). A single ClassAlive classification fully resets the entry (real recovery). When several circuits are open at once the shape is server-wide, not session-specific, so it emits one loud, rate-limited wedge warning naming the manual remedy. It NEVER auto-kills tmux.
func NewReviveBreaker ¶ added in v1.10.9
func NewReviveBreaker(log *slog.Logger) *ReviveBreaker
NewReviveBreaker returns an empty breaker. log may be nil (no logging).
func (*ReviveBreaker) AfterRevive ¶ added in v1.10.9
func (b *ReviveBreaker) AfterRevive(id, title string, actionErr error)
AfterRevive records that a revive action ran this sweep. actionErr != nil is immediately futile; a nil error arms pendingVerify so the next OnClassify can judge whether the session actually stabilized.
func (*ReviveBreaker) OnClassify ¶ added in v1.10.9
func (b *ReviveBreaker) OnClassify(id, title string, class RevivalClass) bool
OnClassify records this sweep's classification for an instance and returns whether a revive should be attempted (only meaningful for ClassErrored).
- ClassAlive → full reset (the session actually recovered). Returns true but the caller ignores it for non-errored classes.
- ClassDead → no attempt, counters untouched (a gone server is a different condition; an errored↔dead flap must not lose futility state).
- ClassErrored → if a revive from a prior sweep is pending verification and the session is still errored, that revive was futile. Then: if the circuit is open and still cooling down, returns false (skip); otherwise returns true (attempt, including the single probe at cooldown expiry).
func (*ReviveBreaker) Prune ¶ added in v1.10.9
func (b *ReviveBreaker) Prune()
Prune drops entries not seen within EntryTTL. Called once per ReviveAll batch.
type ReviveOutcome ¶ added in v1.7.8
type ReviveOutcome struct {
InstanceID string
Title string
Class RevivalClass
Revived bool
Err error
// CircuitOpen is true when the futility circuit breaker skipped this
// instance's revive this sweep because prior revives never stabilized
// (a wedged tmux server that only a manual restart recovers, #1579).
CircuitOpen bool
}
ReviveOutcome describes one instance's revive attempt.
type Reviver ¶ added in v1.7.8
type Reviver struct {
// TmuxExists receives the session's stored socket name so the probe
// targets the right tmux server. Sessions created under an isolated
// socket (Instance.TmuxSocketName != "") would otherwise appear "dead"
// on the default server and the reviver would wrongly mark them gone.
TmuxExists func(name, socketName string) bool
PipeAlive func(name string) bool
ReviveAction func(*Instance) error
Stagger time.Duration
Log *slog.Logger
// Breaker bounds futile reconnect storms on a wedged tmux server (#1579).
// nil disables the breaker entirely — exact legacy behavior, relied on by
// unit tests that construct Reviver{} directly.
Breaker *ReviveBreaker
}
Reviver walks storage and re-establishes dead control pipes for instances whose underlying tmux server is still alive. See REPORT-D-auto-revive.md and .planning/v178-ssh-reviver/PLAN.md for design rationale.
Fields are injectable so tests can stub out tmux and pipe checks without spawning real processes. Production code should use NewReviver() to get sensible defaults.
func NewReviver ¶ added in v1.7.8
func NewReviver() *Reviver
NewReviver returns a Reviver wired to real tmux + PipeManager primitives. Defaults: 500ms stagger between revives to avoid thundering herd on Claude cold-start rate limits when many sessions are errored simultaneously.
func (*Reviver) Classify ¶ added in v1.7.8
func (r *Reviver) Classify(inst *Instance) RevivalClass
Classify decides which bucket an instance falls into at scan time.
TODO(revive-liveness): after a tmux SERVER restart (OOM/SIGKILL + systemd Restart=on-failure, or a manual restart), a session that is in fact alive under the NEW server can briefly probe as not-found if the has-session call races the server coming back up. Today that yields ClassDead, which is never auto-revived — so a transient miss permanently strands a recoverable session until the user restarts it manually. A fix (single short retry on a confirmed-not-found probe, scoped to revive so it does not add latency to the shared HasSession hot path) is deferred: it is orthogonal to the data-loss race fixed here and carries its own regression risk for the watchPipe reconnect loop that shares the probe. The data-loss-critical half (revive never clobbering concurrently-added rows) is fixed via Storage.PersistRevivedInstances.
func (*Reviver) ReviveAll ¶ added in v1.7.8
func (r *Reviver) ReviveAll(instances []*Instance) []ReviveOutcome
ReviveAll walks instances, classifies each, and triggers ReviveAction for those in ClassErrored. Calls are staggered by r.Stagger. Alive/dead entries do NOT consume a stagger slot — total wall clock scales with errored count, not total count.
MUTATION INVARIANT (relied on by the CLI persist path, revive_cmd.go): ReviveAll mutates an instance ONLY when it both (a) classifies it ClassErrored AND (b) its ReviveAction succeeds (outcome.Revived == true). ClassAlive and ClassDead instances are classified and returned untouched — no status flip, no timestamp/socket normalization. The only field a successful revive writes is Instance.Status (StatusError → StatusRunning; see defaultReviveAction). The caller therefore persists exactly the Revived subset, status-only, and nothing else. If a future ReviveAction starts normalizing already-alive sessions or mutating other fields, this invariant — and the targeted persist in runReviveAll / PersistRevivedInstances — MUST be revisited so those mutations are not silently dropped.
func (*Reviver) ReviveOne ¶ added in v1.7.8
func (r *Reviver) ReviveOne(inst *Instance) ReviveOutcome
ReviveOne runs a single-instance revive cycle. Used by the CLI --name flag.
type SSHRunner ¶ added in v0.20.0
type SSHRunner struct {
Host string // SSH destination (e.g., "user@host")
AgentDeckPath string // Remote agent-deck binary path
Profile string // Remote profile name
// contains filtered or unexported fields
}
SSHRunner executes commands on a remote host via SSH.
func NewSSHRunner ¶ added in v0.20.0
func NewSSHRunner(name string, rc RemoteConfig) *SSHRunner
NewSSHRunner creates an SSHRunner from a RemoteConfig.
func (*SSHRunner) Attach ¶ added in v0.20.0
Attach connects interactively to a remote agent-deck session. Uses a local PTY so that SSH can detect the terminal dimensions and propagate them to the remote side. Handles SIGWINCH to keep the remote PTY in sync when the local terminal is resized, and sends SIGWINCH to self on detach so Bubble Tea re-queries the terminal size.
func (*SSHRunner) CheckBinary ¶ added in v0.21.0
CheckBinary reports the version of agent-deck as found on the remote's $PATH. Returns found=false if the binary is not installed / not on $PATH.
func (*SSHRunner) CreateSession ¶ added in v0.21.0
CreateSession creates and starts a quick new session on the remote, returning its ID.
func (*SSHRunner) CreateSessionWithOptions ¶ added in v1.9.55
func (r *SSHRunner) CreateSessionWithOptions(ctx context.Context, tool, title, path, group string) (string, error)
CreateSessionWithOptions creates and starts a new session on the remote with an explicit tool/title/path/group from the new-session dialog (#1353), returning its ID. Empty values fall back to remote defaults (see remoteAddArgs).
func (*SSHRunner) DeleteSession ¶ added in v0.25.0
DeleteSession removes a session on the remote host.
func (*SSHRunner) DeployBinary ¶ added in v0.21.0
DeployBinary streams binaryData to remotePath on the remote, creating the parent directory and marking it executable. It pipes through `ssh "cat > ..."` rather than scp so the remote shell handles the path uniformly; remotePath is expected to be absolute (see ResolveRemotePath) (#1171).
func (*SSHRunner) DetectPlatform ¶ added in v0.21.0
DetectPlatform returns the remote host's OS and architecture (e.g., "linux", "amd64").
func (*SSHRunner) FetchCostSummary ¶ added in v1.9.24
FetchCostSummary retrieves the remote agent-deck's cost summary as JSON. #1101: the local TUI's status-line cost segment used to show only events written to the local cost_events table — remote sessions' Stop hooks write to the remote DB, so their spend never surfaced locally. The TUI calls this per configured remote and folds the totals into the displayed figures.
Returns nil with no error when the remote returns empty output (older agent-deck builds that predate `costs summary --json`). Callers should treat a nil summary as "remote not available; render local-only totals".
func (*SSHRunner) FetchSessionOutput ¶ added in v1.7.31
FetchSessionOutput retrieves the last response content for a remote session.
func (*SSHRunner) FetchSessionPane ¶ added in v1.9.24
FetchSessionPane retrieves the tmux capture-pane content for a remote session. #1101: Local TUI previews render capture-pane content (ANSI + tool UI chrome). Remote previews used to fetch only the parsed transcript text via FetchSessionOutput, which is why claude-formatted output never showed for SSH sessions. FetchSessionPane closes that gap by asking the remote for the raw pane content via `session output --pane --json`.
func (*SSHRunner) FetchSessions ¶ added in v0.20.0
func (r *SSHRunner) FetchSessions(ctx context.Context) ([]RemoteSessionInfo, error)
FetchSessions retrieves the session list from the remote agent-deck instance.
func (*SSHRunner) InstallBinary ¶ added in v1.9.32
func (r *SSHRunner) InstallBinary(ctx context.Context, binaryData []byte, expectedVersion string) error
InstallBinary resolves the remote's real agent-deck path, deploys binaryData there, then verifies the remote actually runs expectedVersion from its $PATH. It returns an actionable error instead of a false success when the deployed binary is not the one the remote executes (#1171).
func (*SSHRunner) MeasureLatency ¶ added in v1.9.24
MeasureLatency measures the round-trip time of a lightweight noop call to the remote agent-deck binary. Returns the elapsed duration on success.
Implementation note: we run `agent-deck --version` because it is the cheapest possible call (no DB read, no tmux probe, no network back to services). The ControlMaster socket is persisted across calls so we measure mostly network RTT after the first hit, which is exactly what the user wants to see in the header per #1103.
func (*SSHRunner) OpenStream ¶ added in v1.9.25
func (r *SSHRunner) OpenStream(ctx context.Context, args ...string) (io.WriteCloser, func() error, error)
OpenStream spawns a single long-running remote `agent-deck <args...>` subprocess over SSH and returns its stdin pipe + a close function that terminates the subprocess. Used by #1112 bug 2's persistent insert-mode stream so 100 keystrokes amortize to one ssh fork+exec instead of 100.
The returned WriteCloser is goroutine-safe at the OS pipe layer; the caller is responsible for serializing if it needs message-level ordering (RemoteKeySender does this with its own mutex).
func (*SSHRunner) ResolveRemotePath ¶ added in v1.9.32
ResolveRemotePath determines the absolute filesystem path the remote actually executes agent-deck from. This is the heart of the #1171 fix: deploying to a bare relative name ("agent-deck") landed the binary in ~/agent-deck while the remote ran ~/.local/bin/agent-deck from its $PATH. Resolution order:
- an explicit agent_deck_path from config (with ~ expanded), else
- `command -v agent-deck` — the binary the remote's $PATH actually runs, else
- the install.sh default: $HOME/.local/bin/agent-deck.
func (*SSHRunner) RestartSession ¶ added in v0.25.0
RestartSession restarts a session on the remote host.
func (*SSHRunner) Run ¶ added in v0.20.0
Run executes an agent-deck command on the remote host and returns stdout.
func (*SSHRunner) RunCommand ¶ added in v0.20.0
RunCommand executes an arbitrary agent-deck command on the remote.
type SandboxConfig ¶ added in v0.19.17
type SandboxConfig struct {
// Enabled indicates the session runs inside a container.
Enabled bool `json:"enabled"`
// Image is the Docker image name (e.g. "ghcr.io/asheshgoplani/agent-deck-sandbox:latest").
Image string `json:"image"`
// CPULimit is the optional CPU quota for the container (e.g. "2.0").
CPULimit *string `json:"cpu_limit,omitempty"`
// MemoryLimit is the optional memory cap for the container (e.g. "4g").
MemoryLimit *string `json:"memory_limit,omitempty"`
// ExtraVolumes maps host paths to container paths for additional bind mounts.
ExtraVolumes map[string]string `json:"extra_volumes,omitempty"`
}
SandboxConfig holds per-session Docker sandbox settings.
func NewSandboxConfig ¶ added in v0.19.17
func NewSandboxConfig(imageOverride string) *SandboxConfig
NewSandboxConfig builds a SandboxConfig from CLI flags and user settings. imageOverride takes precedence; when empty the global default image is used. CPU and memory limits are applied from DockerSettings when configured.
type SearchEntry ¶ added in v0.5.3
type SearchEntry struct {
SessionID string // Claude session UUID
FilePath string // Path to .jsonl file
CWD string // Project working directory
Summary string // First user message or summary
ModTime time.Time // File modification time
FileSize int64 // File size in bytes
// contains filtered or unexported fields
}
SearchEntry represents a searchable Claude session
func (*SearchEntry) ContentPreview ¶ added in v0.10.0
func (e *SearchEntry) ContentPreview(max int) string
func (*SearchEntry) ContentString ¶ added in v0.10.0
func (e *SearchEntry) ContentString() string
func (*SearchEntry) GetSnippet ¶ added in v0.5.3
func (e *SearchEntry) GetSnippet(query string, windowSize int) string
GetSnippet extracts a context window around the first match Uses rune-based indexing to safely handle UTF-8 content Optimized: Single rune conversion instead of triple conversion
func (*SearchEntry) Match ¶ added in v0.5.3
func (e *SearchEntry) Match(query string) []MatchRange
Match searches for query in entry content (case-insensitive) Returns match positions for highlighting
func (*SearchEntry) MatchCount ¶ added in v0.10.0
func (e *SearchEntry) MatchCount(query string) int
type SearchResult ¶ added in v0.5.3
type SearchResult struct {
Entry *SearchEntry
Matches []MatchRange
Score int
Snippet string
}
SearchResult represents a search result with match info
type SearchTier ¶ added in v0.5.3
type SearchTier int
SearchTier represents the search strategy tier
const ( TierInstant SearchTier = iota // < 100MB, full in-memory TierBalanced // 100MB-500MB, on-demand scan to cap memory )
func DetectTier ¶ added in v0.5.3
func DetectTier(totalSize int64) SearchTier
DetectTier determines the appropriate search tier based on data size
type SelfHealSettings ¶ added in v1.9.67
type SelfHealSettings struct {
// Enabled is the global kill switch (§3.7). When false (the default),
// self-heal does nothing at all — not even observe-mode logging. Set true to
// run the observe-only Stage 1.
Enabled bool `toml:"enabled,omitempty"`
// Mode is the authority level: "observe" (default, the only acting mode in
// v1.9.67 — logs would_have, takes no action), "single_action" / "full"
// (Stages 2-3, DEFINED but GUARDED, refuse to act). An unknown/empty value
// is normalized to "observe".
Mode string `toml:"mode,omitempty"`
// AuditPath overrides where the durable NDJSON audit log lands. Empty uses
// the per-profile default under the agent-deck data dir (see
// SelfHealAuditPath). The audit is the dataset reviewed over the ≥1-week
// observe window before any Stage-2 re-approval.
AuditPath string `toml:"audit_path,omitempty"`
// PerSessionPerWindow overrides the per-session recovery cap (default 2 / 6h;
// auth_401 is always 1). 0 uses the default. Starting dial; tuned from
// observe data.
PerSessionPerWindow int `toml:"per_session_per_window,omitzero"`
// GlobalPerHour overrides the fleet-wide hourly recovery cap (default 5 =
// TriageMaxPerHour). 0 uses the default.
GlobalPerHour int `toml:"global_per_hour,omitzero"`
// OptOutGroups lists group paths that opt OUT of self-heal entirely
// (deliberate long-waiting stream leads, sensitive scopes — §3.7). Checked
// in the stuck predicate as a quick disqualifier.
OptOutGroups []string `toml:"opt_out_groups,omitempty"`
// OptOutSessions lists session ids/titles that opt OUT of self-heal.
OptOutSessions []string `toml:"opt_out_sessions,omitempty"`
}
SelfHealSettings controls the self-heal supervision policy (SELF-HEAL-DESIGN.md §3.7, §6). The shipped default is fully observe-only: it detects truly-stuck sessions, exercises the safety state-machine, and LOGS what it would do — taking ZERO recovery action. Modes single_action / full are DEFINED but GUARDED (they refuse to act) until Stages 2-3 are re-approved by Ashesh + the three §9 gap-fixes land.
func GetSelfHealSettings ¶ added in v1.9.67
func GetSelfHealSettings() SelfHealSettings
GetSelfHealSettings returns self-heal settings from config. The zero value (Enabled=false) is the safe default: self-heal does nothing unless explicitly enabled. Mode is normalized to a known value by SelfHealMode().
func (SelfHealSettings) IsGroupOptedOut ¶ added in v1.9.67
func (s SelfHealSettings) IsGroupOptedOut(groupPath string) bool
IsGroupOptedOut reports whether a group path opts out of self-heal.
func (SelfHealSettings) IsSessionOptedOut ¶ added in v1.9.67
func (s SelfHealSettings) IsSessionOptedOut(id, title string) bool
IsSessionOptedOut reports whether a session (by id or title) opts out.
func (SelfHealSettings) SelfHealMode ¶ added in v1.9.67
func (s SelfHealSettings) SelfHealMode() string
SelfHealMode normalizes the configured mode to a known value. Empty / unknown → "observe" (the safe default). Used by the daemon when constructing the engine. The string return matches selfheal.Mode values.
type SessionAnalytics ¶ added in v0.8.27
type SessionAnalytics struct {
// Token usage (cumulative across all turns)
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
CacheReadTokens int `json:"cache_read_input_tokens"`
CacheWriteTokens int `json:"cache_creation_input_tokens"`
// Current context size (last turn's input + cache read tokens)
// This represents the actual context window usage, not cumulative totals
CurrentContextTokens int `json:"current_context_tokens"`
// Session metrics
TotalTurns int `json:"total_turns"`
Duration time.Duration `json:"duration"`
StartTime time.Time `json:"start_time"`
LastActive time.Time `json:"last_active"`
// Tool usage
ToolCalls []ToolCall `json:"tool_calls"`
// Model ID from the last assistant message (e.g. "claude-opus-4-6")
Model string `json:"model,omitempty"`
// Subagents
Subagents []SubagentInfo `json:"subagents"`
// Cost estimation
EstimatedCost float64 `json:"estimated_cost"`
// 5-hour billing blocks
BillingBlocks []BillingBlock `json:"billing_blocks"`
}
SessionAnalytics holds parsed session metrics from Claude JSONL files
func ParseSessionJSONL ¶ added in v0.8.27
func ParseSessionJSONL(path string) (*SessionAnalytics, error)
ParseSessionJSONL parses a Claude session JSONL file and returns analytics
func (*SessionAnalytics) CalculateCost ¶ added in v0.8.27
func (a *SessionAnalytics) CalculateCost(model string) float64
CalculateCost estimates session cost based on token usage and model pricing
func (*SessionAnalytics) ContextPercent ¶ added in v0.8.27
func (a *SessionAnalytics) ContextPercent(modelLimit int) float64
ContextPercent returns the percentage of context window used Uses CurrentContextTokens (last turn's input + cache) for accurate context usage modelLimit is the model's context window size; if 0, it is inferred from the Model field
func (*SessionAnalytics) TotalTokens ¶ added in v0.8.27
func (a *SessionAnalytics) TotalTokens() int
TotalTokens returns the sum of all token types
type SessionBudget ¶ added in v0.26.4
type SessionBudget struct {
TotalLimit float64 `toml:"total_limit,omitzero"`
}
type SessionIDLifecycleEvent ¶ added in v0.28.0
type SessionIDLifecycleEvent struct {
InstanceID string `json:"instance_id"`
Tool string `json:"tool"`
Action string `json:"action"` // bind | rebind | reject | scan_disabled
Source string `json:"source"` // tmux_env | hook_payload | hook_anchor | disk_scan
OldID string `json:"old_id,omitempty"`
NewID string `json:"new_id,omitempty"`
Candidate string `json:"candidate,omitempty"`
Reason string `json:"reason,omitempty"`
HookEvent string `json:"hook_event,omitempty"`
Timestamp int64 `json:"ts"`
}
SessionIDLifecycleEvent captures every session-ID bind/rebind/reject decision. Events are appended as JSONL for postmortem debugging.
type SessionLifecycleEvent ¶ added in v1.9.29
type SessionLifecycleEvent struct {
InstanceID string `json:"instance_id"`
Action string `json:"action"` // currently only "idle-timeout-expired"
Reason string `json:"reason,omitempty"`
Timestamp int64 `json:"ts"`
}
SessionLifecycleEvent is a single row in session-lifecycle.jsonl.
type ShellSettings ¶ added in v0.8.77
type ShellSettings struct {
// EnvFiles is a list of .env files to source for ALL sessions
// Paths can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
// Files are sourced in order; later files override earlier ones
EnvFiles []string `toml:"env_files,omitempty"`
// InitScript is an optional shell script or command to run before each session
// Useful for direnv, nvm, pyenv, etc.
// Can be a file path (e.g., "~/.agent-deck/init.sh") or inline command
// (e.g., 'eval "$(direnv hook bash)"')
InitScript string `toml:"init_script,omitempty"`
// IgnoreMissingEnvFiles silently ignores missing .env files (default: true)
// When false, sessions will error if an env_file doesn't exist
IgnoreMissingEnvFiles *bool `toml:"ignore_missing_env_files,omitempty"`
// ExitToShell, when true, wraps built-in agent spawn commands so that
// exiting the agent (e.g. `/exit` from Claude Code) drops the pane back to
// an interactive shell at the same cwd instead of the pane dying / the TUI
// auto-restarting. This restores the pre-#503 workflow: exit → do shell-only
// work (aws-vault exec, direnv, …) → `claude --resume` the same session.
// Default: false (opt-in). Issue #1161, design doc
// docs/decisions/1161-exit-to-shell-then-resume.md.
ExitToShell *bool `toml:"exit_to_shell,omitempty"`
// LaunchShell, when true, wraps agent spawn commands with an interactive
// shell invocation so that environment variables from ~/.zshrc, ~/.bashrc
// etc. are available to the agent process. This solves the issue where
// OpenCode MCP configs with {env:VAR} references fail when launched from
// the TUI because the agent doesn't inherit the interactive shell's
// environment.
// Default: false (opt-in). Issue #1218.
LaunchShell *bool `toml:"launch_shell,omitempty"`
}
ShellSettings defines shell environment configuration for sessions
func (*ShellSettings) GetExitToShell ¶ added in v1.9.40
func (s *ShellSettings) GetExitToShell() bool
GetExitToShell returns whether agent sessions should fall back to an interactive shell on agent exit, defaulting to false (opt-in). Issue #1161.
func (*ShellSettings) GetIgnoreMissingEnvFiles ¶ added in v0.8.77
func (s *ShellSettings) GetIgnoreMissingEnvFiles() bool
GetIgnoreMissingEnvFiles returns whether to ignore missing env files, defaulting to true
func (*ShellSettings) GetLaunchShell ¶ added in v1.9.47
func (s *ShellSettings) GetLaunchShell() bool
GetLaunchShell returns whether agent commands should be wrapped with a shell invocation that loads startup files before launch, defaulting to false (opt-in). Issue #1218.
type SkillCandidate ¶ added in v0.19.1
type SkillCandidate struct {
ID string `json:"id"` // source/name
Name string `json:"name"`
Source string `json:"source"`
SourcePath string `json:"source_path"`
EntryName string `json:"entry_name"` // directory/file name in source
Description string `json:"description,omitempty"`
Kind string `json:"kind"` // "dir" or "file"
}
SkillCandidate is a discovered skill from one source.
func ListAvailableSkills ¶ added in v0.19.1
func ListAvailableSkills() ([]SkillCandidate, error)
ListAvailableSkills returns all discovered skills across enabled sources.
func ResolveSkillCandidate ¶ added in v0.19.1
func ResolveSkillCandidate(skillRef, sourceName string) (*SkillCandidate, error)
ResolveSkillCandidate resolves one skill from discovery by name or source/name.
type SkillSource ¶ added in v0.19.1
type SkillSource struct {
Name string `json:"name"`
Path string `json:"path"`
Description string `json:"description,omitempty"`
Enabled bool `json:"enabled"`
}
SkillSource is a resolved source used for display and discovery.
func ListSkillSources ¶ added in v0.19.1
func ListSkillSources() ([]SkillSource, error)
ListSkillSources returns sorted source definitions for display.
type SkillSourceDef ¶ added in v0.19.1
type SkillSourceDef struct {
Path string `toml:"path"`
Description string `toml:"description,omitempty"`
Enabled *bool `toml:"enabled,omitempty"`
}
SkillSourceDef defines a named source path for discovering skills.
func (SkillSourceDef) IsEnabled ¶ added in v0.19.1
func (s SkillSourceDef) IsEnabled() bool
IsEnabled returns true when the source should be considered during discovery.
type SkillSourcesConfig ¶ added in v0.19.1
type SkillSourcesConfig struct {
Sources map[string]SkillSourceDef `toml:"sources"`
}
SkillSourcesConfig is persisted in ~/.agent-deck/skills/sources.toml.
type SlackSettings ¶ added in v0.16.0
type SlackSettings struct {
// BotToken is the Slack bot token (xoxb-...)
BotToken string `toml:"bot_token,omitempty"`
// AppToken is the Slack app-level token for Socket Mode (xapp-...)
AppToken string `toml:"app_token,omitempty"`
// ChannelID is the Slack channel where the bot listens and posts (C01234...)
ChannelID string `toml:"channel_id,omitempty"`
// ListenMode controls when the bot responds: "mentions" (only @mentions) or "all" (all channel messages)
// Default: "mentions"
ListenMode string `toml:"listen_mode,omitempty"`
// AllowedUserIDs is a list of Slack user IDs authorized to use the bot.
// If empty, all users are allowed (backward compatible).
// Get user ID from Slack: Right-click user → View profile → More → Copy member ID
AllowedUserIDs []string `toml:"allowed_user_ids,omitempty"`
}
SlackSettings defines Slack bot configuration for the conductor bridge
type SpawnAttempt ¶ added in v1.9.19
type SpawnAttempt struct {
// InstanceID is the lock-key partition. Different instances do not
// serialize against each other.
InstanceID string
// AlreadyAlive is an optional supplementary gate. When non-nil and
// it returns true, Run() skips Spawn even if no stamp exists. Used
// by callers that already know the spawn is unnecessary (e.g. CLI
// `session start` pre-checks `Exists()`); leave nil to let the
// stamp logic decide.
AlreadyAlive func() bool
// Spawn is the protected critical section. Run() invokes it exactly
// once across concurrent callers in a storm window, and never if a
// sibling already completed while we were waiting on the lock.
Spawn func() error
}
SpawnAttempt is the single-flight wrapper used by Restart(), Start(), and StartWithMessage(). The lock acquisition + sibling-detect window is implicit — call Run().
The storm-discriminator is *time*, not "is a session alive". A legitimate manual restart of a long-running session must proceed even though tmux holds a live AGENTDECK_INSTANCE_ID-matching session; only the *storm* shape — multiple spawns racing while one is in-flight — should be suppressed. Run() captures a timestamp before acquiring the lock and consults the per-instance spawn-stamp after acquisition. If a sibling completed during our wait (stamp mtime > our pre-lock time), we skip; otherwise we run Spawn and stamp on success.
func (SpawnAttempt) Run ¶ added in v1.9.19
func (a SpawnAttempt) Run() error
Run acquires the per-instance spawn lock, gates on a "spawned-while- we-waited" check, and invokes Spawn. Returns the first non-nil error from any step.
type SpawnFailureRecord ¶ added in v1.10.9
type SpawnFailureRecord struct {
InstanceID string `json:"instance_id"`
Tool string `json:"tool"`
Command string `json:"command,omitempty"`
Reason string `json:"reason"` // tmux_start_failed | spawn_died_fast
DyingOutput string `json:"dying_output,omitempty"` // last pane snapshot captured while alive
ElapsedMs int64 `json:"elapsed_ms"` // ms from spawn to observed death (0 for tmux_start_failed)
Timestamp int64 `json:"ts"`
}
SpawnFailureRecord captures why a session's initial process died at or shortly after spawn. Issue #1580: when a custom command (e.g. a broken `codex.command` npx wrapper) exits immediately, tmux tears the whole session down and takes the pane output with it. Status detection later only sees "tmux session missing" → StatusError, so the TUI shows a bare "error" with no preview and nothing in the logs. This record is written to disk as a durable sidecar the moment the death is observed, so the failure survives the process boundary and can be surfaced in the preview pane, `session show`, and the lifecycle log.
func (*SpawnFailureRecord) FormatForDisplay ¶ added in v1.10.9
func (r *SpawnFailureRecord) FormatForDisplay() string
FormatForDisplay renders the record as a human-readable block for the preview pane and `session show`.
type Status ¶
type Status string
Status represents the current state of a session
const ( StatusRunning Status = "running" StatusWaiting Status = "waiting" StatusIdle Status = "idle" StatusError Status = "error" StatusStarting Status = "starting" // Session is being created (tmux initializing) StatusStopped Status = "stopped" // Session intentionally stopped by user (not crashed) // StatusQueued: session is waiting for group capacity. v1.9.1 introduces // group max_concurrent caps; a launch into a group at cap stores the // instance with this status and starts it once a running session ends. StatusQueued Status = "queued" )
type StatusEvent ¶ added in v0.18.0
type StatusEvent struct {
InstanceID string `json:"instance_id"`
Title string `json:"title"`
Tool string `json:"tool"`
Status string `json:"status"`
PrevStatus string `json:"prev_status"`
Timestamp int64 `json:"ts"`
}
StatusEvent represents a session status change event. Written atomically to ~/.agent-deck/events/ by both the hook handler (Claude) and the TUI's background status poller (all tools).
type StatusEventWatcher ¶ added in v0.18.0
type StatusEventWatcher struct {
// contains filtered or unexported fields
}
StatusEventWatcher watches ~/.agent-deck/events/ for new status events using fsnotify. Delivers parsed StatusEvent structs via a channel.
func NewStatusEventWatcher ¶ added in v0.18.0
func NewStatusEventWatcher(filterInstanceID string) (*StatusEventWatcher, error)
NewStatusEventWatcher creates a watcher for the events directory. If filterInstanceID is non-empty, only events for that instance are delivered. Call Start() in a goroutine, then read from EventCh().
func (*StatusEventWatcher) EventCh ¶ added in v0.18.0
func (w *StatusEventWatcher) EventCh() <-chan StatusEvent
EventCh returns the channel that delivers parsed status events.
func (*StatusEventWatcher) Start ¶ added in v0.18.0
func (w *StatusEventWatcher) Start()
Start begins watching the events directory. Must be called in a goroutine. Blocks until Stop() is called or the context is cancelled.
func (*StatusEventWatcher) Stop ¶ added in v0.18.0
func (w *StatusEventWatcher) Stop()
Stop shuts down the watcher and closes the event channel.
func (*StatusEventWatcher) WaitForStatus ¶ added in v0.18.0
func (w *StatusEventWatcher) WaitForStatus(statuses []string, timeout time.Duration) (StatusEvent, error)
WaitForStatus blocks until an event with one of the given statuses is received, or the timeout expires.
type StatusFileWatcher ¶ added in v0.16.0
type StatusFileWatcher struct {
// contains filtered or unexported fields
}
StatusFileWatcher watches ~/.agent-deck/hooks/ for status file changes and updates instance hook status in real time.
func NewStatusFileWatcher ¶ added in v0.16.0
func NewStatusFileWatcher(onChange func()) (*StatusFileWatcher, error)
NewStatusFileWatcher creates a new watcher for the hooks directory. Call Start() to begin watching.
func (*StatusFileWatcher) ClearHookStatus ¶ added in v1.9.2
func (w *StatusFileWatcher) ClearHookStatus(instanceID string)
ClearHookStatus removes the cached hook status for an instance.
func (*StatusFileWatcher) GetHookStatus ¶ added in v0.16.0
func (w *StatusFileWatcher) GetHookStatus(instanceID string) *HookStatus
GetHookStatus returns the hook status for an instance, or nil if not available.
func (*StatusFileWatcher) Start ¶ added in v0.16.0
func (w *StatusFileWatcher) Start()
Start begins watching the hooks directory. Must be called in a goroutine.
func (*StatusFileWatcher) Stop ¶ added in v0.16.0
func (w *StatusFileWatcher) Stop()
Stop shuts down the watcher.
type StatusSettings ¶ added in v0.11.0
type StatusSettings struct {
// ShellRunningIndicator promotes "shell" tool sessions from idle to
// running when the pane's foreground command is a genuine non-interactive
// process (e.g. "node" from `yarn dev`, "java" from `mvn spring-boot:run`).
// Opt-in (default false): the interactive-program denylist is necessarily
// incomplete, so a shell sitting at a psql/REPL/fzf prompt would otherwise
// read "running" while the user is idle. Users who want dev-server
// detection accept that tradeoff explicitly:
//
// [status]
// shell_running_indicator = true
ShellRunningIndicator bool `toml:"shell_running_indicator"`
}
func GetStatusSettings ¶ added in v0.11.0
func GetStatusSettings() StatusSettings
GetStatusSettings returns status detection settings with defaults applied.
type StopHookDecision ¶ added in v1.9.44
type StopHookDecision struct {
Decision string `json:"decision,omitempty"`
Reason string `json:"reason,omitempty"`
}
StopHookDecision mirrors the Claude Code Stop-hook JSON contract. Decision "block" keeps the turn alive and feeds Reason back as the next turn's input.
func DrainForStopHook ¶ added in v1.9.44
func DrainForStopHook(instanceID string, stopHookActive bool) (StopHookDecision, bool, error)
DrainForStopHook implements the conductor Stop-hook contract for one instance. stopHookActive is Claude Code's flag: true means this Stop is a continuation induced by a previous block (so it counts against the budget); false is a genuine user turn boundary (resets the budget).
Returns the decision to emit, whether it blocked, and any error. When the budget is exhausted it returns no-block WITHOUT draining, so pending records are preserved for the heartbeat path (never lost to the guard).
type Storage ¶
type Storage struct {
// contains filtered or unexported fields
}
Storage handles persistence of session data via SQLite. Thread-safe with mutex protection for concurrent access within a single process. Multiple processes share data via SQLite WAL mode.
func NewStorageWithProfile ¶ added in v0.3.0
NewStorageWithProfile creates a storage instance for a specific profile. If profile is empty, uses the effective profile (from env var or config). Automatically runs migration from old layout if needed, then opens SQLite. If sessions.json exists and state.db is empty, auto-migrates data.
func (*Storage) DeleteGroupSubtree ¶ added in v1.10.9
DeleteGroupSubtree removes a group and all of its descendants from the groups table. SaveGroups is additive (upsert, never prune), so intentional group removal — delete, rename, move — must call this explicitly; otherwise the old path rows linger and the group resurrects on the next reload.
func (*Storage) DeleteInstance ¶ added in v0.11.3
DeleteInstance removes a single instance from the database by ID. This ensures the row is immediately removed, preventing resurrection on reload.
func (*Storage) GetDB ¶ added in v0.11.0
GetDB returns the underlying StateDB for direct access (status writes, heartbeat, etc.)
func (*Storage) GetFileMtime ¶ added in v0.10.15
GetFileMtime returns the database's last-write timestamp, for detecting that another process changed the profile DB.
It deliberately does NOT os.Stat(state.db). SQLite runs in WAL mode here, so a committed write lands in state.db-wal and leaves state.db's mtime unchanged until a checkpoint — statting the main file reports "no change" for every out-of-process write, so the TUI's external-change guard never fired and its next full-table save clobbered CLI writes. metadata.last_modified is the authoritative signal, and the one StorageWatcher already polls.
func (*Storage) GetUpdatedAt ¶ added in v0.6.1
GetUpdatedAt returns the last modification timestamp from SQLite metadata.
func (*Storage) InsertSessionAndVerify ¶ added in v1.9.15
InsertSessionAndVerify performs a durable single-row session insert.
Flow (v1.9.x issue #1031 fix, parallel to #909's RemoveSessionAndVerify):
- SaveInstance(row) — targeted INSERT OR REPLACE on the single new row only, NOT a full-table rewrite. This sidesteps the load-modify-write race where a sibling launch's `DELETE FROM instances WHERE id NOT IN (...)` inside SaveInstances would silently delete this row.
- SaveGroupsOnly(groupTree) — persist any group structure changes WITHOUT rewriting the instances table. Rewriting (SaveWithGroups) is the load-modify-write pattern that lets a concurrent launch drop this row; skipping it eliminates the structural race for our own write.
- Verify InstanceExists(id) is true. If not (some other process issued a SaveInstances rewrite that excluded this row because it loaded the instances slice pre-INSERT), re-issue the targeted INSERT and loop with linear backoff.
- After exhausting attempts, return ErrInsertNotPersistent so the caller can fail loudly instead of returning success on a row that's not actually there.
instances is the post-insert session list, used only to compute group sort_order / membership for SaveGroupsOnly. groupTree may be nil if the caller doesn't care to persist groups.
func (*Storage) InstanceExists ¶ added in v1.9.1
InstanceExists returns true iff a row with the given id is currently persisted. Used by RemoveSessionAndVerify to confirm a DELETE actually landed (issue #909).
func (*Storage) LoadLite ¶ added in v0.8.95
func (s *Storage) LoadLite() ([]*InstanceData, []*GroupData, error)
LoadLite reads session data from SQLite without tmux reconnection. This is a fast path for operations that only need to read session metadata (e.g., finding current session by tmux name) without initializing full Instance objects. Returns raw InstanceData and GroupData without any subprocess calls.
func (*Storage) LoadRecentSessions ¶ added in v0.20.0
func (s *Storage) LoadRecentSessions() ([]*statedb.RecentSessionRow, error)
LoadRecentSessions returns recently deleted session configs for the picker.
func (*Storage) LoadWithGroups ¶
LoadWithGroups reads instances and groups from SQLite, reconnects tmux sessions.
func (*Storage) PersistRevivedInstances ¶ added in v1.9.48
PersistRevivedInstances durably persists the status heal from a revive sweep WITHOUT the full-table rewrite that SaveWithGroups performs and WITHOUT a full-row INSERT OR REPLACE.
Why this exists (two races, two guarantees):
Lost-update vs. concurrent `add`. `session revive` is a read-process-write cycle: it loads the full instances snapshot, classifies/heals each one (flipping StatusError → StatusRunning), then persists. If it persisted via SaveWithGroups → SaveInstances, that path's `DELETE FROM instances WHERE id NOT IN (<snapshot ids>)` sweep would delete any session a concurrent process (TUI/CLI `add`) inserted AFTER revive loaded its snapshot — the row is silently lost because it was never in revive's stale id set. The targeted path below issues NO sweep, so rows revive never saw are never touched.
Clobber vs. concurrent edit of a row being revived. A full-row write (INSERT OR REPLACE / saveSingleInstance) would push EVERY column from revive's stale in-memory snapshot, overwriting any field (title, group, tool_data, last_accessed, claude_session_id, …) a concurrent process edited between revive's load and its save. Revive owns exactly ONE field: Instance.Status (see reviver.go defaultReviveAction — it mutates nothing else). So this method persists ONLY status, via the targeted `UPDATE instances SET status = ? WHERE id = ?` batch (PersistInstanceStatusesTx), inside a single transaction for atomicity.
Callers should pass only the instances they actually revived; rows whose id is absent are simply not in the batch and stay untouched.
func (*Storage) RemoveSessionAndVerify ¶ added in v1.9.1
func (s *Storage) RemoveSessionAndVerify(id string, remainingInstances []*Instance, groupTree *GroupTree) error
RemoveSessionAndVerify performs a durable session removal.
Flow (v1.9.1 issue #909 fix):
- DeleteInstance(id) — targeted DELETE, busy-retry inside statedb.
- SaveGroupsOnly(groupTree) — persist any group structure changes WITHOUT rewriting the instances table. Rewriting (SaveWithGroups) is the load-modify-write pattern that lets a concurrent rm resurrect this row via INSERT OR REPLACE; skipping it eliminates the structural race for our own write.
- Verify InstanceExists(id) is false. If still present (because some other process did a SaveInstances rewrite that included the row), re-issue the targeted DELETE and loop with linear backoff.
- After exhausting attempts, return ErrRemovalNotPersistent so the caller can fail loudly instead of printing "✓ Removed" on a row that's still there.
remainingInstances is the post-removal session list, used only to compute group sort_order / membership for SaveGroupsOnly. groupTree may be nil if the caller doesn't care to persist groups.
func (*Storage) Save ¶
Save persists instances to SQLite DEPRECATED: Use SaveWithGroups to ensure groups are not lost
func (*Storage) SaveGroupsOnly ¶ added in v0.11.3
SaveGroupsOnly persists only the groups table to SQLite. This is a lightweight save for visual state like group expanded/collapsed. It does NOT call Touch() to avoid triggering StorageWatcher reloads on other instances.
func (*Storage) SaveRecentSession ¶ added in v0.20.0
SaveRecentSession captures a deleted session's config for quick re-creation.
func (*Storage) SaveWithGroups ¶
SaveWithGroups persists instances and groups to SQLite. Converts Instance objects to database rows, then batch-upserts in a transaction.
UPSERT-ONLY (#1550): this path never deletes rows. It used to route through statedb.SaveInstances, whose `DELETE FROM instances WHERE id NOT IN (...)` sweep let any process holding a stale snapshot silently delete sessions a concurrent process created after that snapshot was loaded (the TUI-side twin of #909/#1031). Deletions must be explicit and targeted instead: DeleteInstance / RemoveSessionAndVerify at the moment the user deletes a session, or statedb.ClearAllInstances for an intentional full wipe.
func (*Storage) SyncInstanceCwd ¶ added in v1.10.9
SyncInstanceCwd updates the persisted project_path for id to newCwd when they differ. If newCwd matches an entry in the instance's additional_paths, positions are swapped so the multi-repo primary reflects Claude's current working directory. Reports whether the instance was found in this profile.
func (*Storage) UpdateTitleIfUnlocked ¶ added in v1.10.9
UpdateTitleIfUnlocked sets an instance's title with a single conditional UPDATE that only applies while the row is still unlocked at write time — see StateDB.UpdateTitleIfUnlocked for why this must be a targeted write rather than a full instance-list round-trip.
func (*Storage) WriteAutoNameDescription ¶ added in v1.9.58
WriteAutoNameDescription persists a single auto-named session's last captured Claude task description via a targeted column update — no whole-row rewrite, no full-table reconcile (see statedb.WriteAutoNameDescription). This lets the archive path snapshot the display name without a full save.
type StorageData ¶
type StorageData struct {
Instances []*InstanceData `json:"instances"`
Groups []*GroupData `json:"groups,omitempty"` // Persist empty groups
UpdatedAt time.Time `json:"updated_at"`
}
StorageData represents the JSON structure for persistence (kept for migration/compat)
type StreamConfig ¶ added in v1.7.48
type StreamConfig struct {
// IdleTimeout is the max time without any new record before the
// streamer emits an error event and returns. Default: 10s.
IdleTimeout time.Duration
// CharBudget is the max chars buffered in the text accumulator before
// a flush. Default: 4000.
CharBudget int
// ToolBudget is the max queued tool events (tool_use + tool_result)
// before buffered text is flushed. Default: 3.
ToolBudget int
// PollInterval is how often the tail loop checks the file for new
// bytes. Default: 100ms.
PollInterval time.Duration
}
StreamConfig controls streamer behavior. Zero values resolve to the approved defaults via WithDefaults(): 10s idle, 4000 chars, 3 tools.
func (StreamConfig) WithDefaults ¶ added in v1.7.48
func (c StreamConfig) WithDefaults() StreamConfig
WithDefaults returns a StreamConfig with zero-valued fields replaced by the approved defaults. Negative values are also treated as zero.
type StreamEvent ¶ added in v1.7.48
type StreamEvent struct {
Type string `json:"type"` // start | text | tool_use | tool_result | stop | error
SchemaVersion string `json:"schema_version,omitempty"` // only set on "start"
SessionID string `json:"session_id,omitempty"` // set on "start"
Timestamp string `json:"ts,omitempty"` // RFC3339Nano when available
MessageID string `json:"message_id,omitempty"` // assistant message id (text / tool_use)
Delta string `json:"delta,omitempty"` // flushed text block
ToolUseID string `json:"tool_use_id,omitempty"`
Name string `json:"name,omitempty"` // tool name on tool_use
Input json.RawMessage `json:"input,omitempty"` // tool input on tool_use
Content json.RawMessage `json:"content,omitempty"`
Reason string `json:"reason,omitempty"` // stop reason
Message string `json:"message,omitempty"` // error payload
}
StreamEvent is one structured event emitted by the streamer, serialized as one JSONL line on the output writer.
type StreamingRemoteKeySender ¶ added in v1.9.25
type StreamingRemoteKeySender struct {
// contains filtered or unexported fields
}
StreamingRemoteKeySender amortizes the per-keystroke ssh fork+exec cost (#1112 bug 2) across an entire insert-mode session by holding one long-running `ssh ... agent-deck session send-keys <id> --stream` subprocess open. Each Send writes one line to that subprocess's stdin — sub-millisecond regardless of network RTT, because the SSH transport is already negotiated.
Wire protocol (matches cmd/agent-deck/session_send_keys_cmd.go's runSendKeysStream):
T <hex-text>\n — SendKeys(decode(<hex-text>)) N <name>\n — SendNamedKey(<name>) E\n — SendEnter()
Hex encoding for text means newlines/NULL/non-printables round-trip safely without escape-sequence gymnastics.
func OpenStreamingRemoteKeySender ¶ added in v1.9.25
func OpenStreamingRemoteKeySender(runner *SSHRunner, sessionID string, ctx context.Context) (*StreamingRemoteKeySender, error)
OpenStreamingRemoteKeySender spawns the persistent SSH stream and returns a sender ready to dispatch. Falls back to a per-call RemoteKeySender if the stream can't open (caller can check via errors.Is — the streaming type implements the same insertKeySender interface as RemoteKeySender).
func (*StreamingRemoteKeySender) Close ¶ added in v1.9.25
func (s *StreamingRemoteKeySender) Close() error
Close terminates the persistent stream. Idempotent.
func (*StreamingRemoteKeySender) SendEnter ¶ added in v1.9.25
func (s *StreamingRemoteKeySender) SendEnter() error
SendEnter writes "E\n" to the stream.
func (*StreamingRemoteKeySender) SendKeys ¶ added in v1.9.25
func (s *StreamingRemoteKeySender) SendKeys(text string) error
SendKeys writes a "T <hex>\n" line to the persistent stream. Empty text is a no-op (matches RemoteKeySender).
func (*StreamingRemoteKeySender) SendNamedKey ¶ added in v1.9.25
func (s *StreamingRemoteKeySender) SendNamedKey(key string) error
SendNamedKey writes "N <key>\n" to the stream. Empty key fails fast.
type SubagentInfo ¶ added in v0.8.27
type SubagentInfo struct {
ID string `json:"id"`
StartTime time.Time `json:"start_time"`
Turns int `json:"turns"`
}
SubagentInfo holds metadata about a subagent spawned during a session
type Substate ¶ added in v1.9.66
Substate is the additive Honest-Status-v2 refinement of a session's coarse status (see tmux.Substate). It explains WHY a session is in its status (model-unavailable, auth-401, idle-at-empty-prompt, running) without altering the byte-stable canonical status. Re-exported here so the CLI/TUI can consume it without importing the tmux package directly.
type SystemStatsSettings ¶ added in v0.28.1
type SystemStatsSettings struct {
// Enabled controls whether system stats are collected and displayed (default: true)
Enabled *bool `toml:"enabled,omitempty"`
// RefreshSeconds sets the collection interval in seconds (default: 5, min: 2)
RefreshSeconds int `toml:"refresh_seconds,omitzero"`
// Format controls display density: "compact" (icons), "full" (labels), "minimal" (values only)
Format string `toml:"format,omitempty"`
// Show lists which stats to display: "cpu", "ram", "disk", "load", "gpu", "network"
Show []string `toml:"show,omitempty"`
}
SystemStatsSettings configures the system stats display in the status bar.
func (SystemStatsSettings) GetEnabled ¶ added in v0.28.1
func (s SystemStatsSettings) GetEnabled() bool
GetEnabled returns whether system stats display is enabled (default: true).
func (SystemStatsSettings) GetFormat ¶ added in v0.28.1
func (s SystemStatsSettings) GetFormat() string
GetFormat returns the display format (default: "compact").
func (SystemStatsSettings) GetRefreshSeconds ¶ added in v0.28.1
func (s SystemStatsSettings) GetRefreshSeconds() int
GetRefreshSeconds returns the collection interval, clamped to [2, 300].
func (SystemStatsSettings) GetShow ¶ added in v0.28.1
func (s SystemStatsSettings) GetShow() []string
GetShow returns the list of stats to display. Defaults to cpu, ram, disk, network.
type TelegramChannelEnablementResult ¶ added in v1.9.27
type TelegramChannelEnablementResult struct {
// OK is true when the session has no telegram channel OR the
// effective settings.json has telegram=true. Either case means
// `--channels` will not silently land on a disabled plugin.
OK bool
// Reason describes the failure when OK=false. Empty when OK=true.
// Stable English phrasing — operators grep for substrings.
Reason string
// EffectiveValue is the value found in settings.json
// (true/false/absent). Carried so the warning can name the exact
// drift variant the operator is observing.
EffectiveValue string
}
TelegramChannelEnablementResult is the structured outcome of VerifyTelegramChannelEnabled. OK is the only consumer-relevant bit; Reason carries the human-readable diagnostic for log lines.
func VerifyTelegramChannelEnabled ¶ added in v1.9.27
func VerifyTelegramChannelEnabled(configDir string, channels []string) TelegramChannelEnablementResult
VerifyTelegramChannelEnabled inspects the given config dir's settings.json and reports whether a session whose `--channels` references `plugin:telegram@…` will find a live MCP transport.
The check is conservative: a missing or unreadable settings.json is treated as "not enabled" (Reason fills in). This matches Claude Code's own behavior — absence equals disabled for channel plugins.
Pure / read-only; safe to call from prepare path and from a runtime monitor (telegram-doctor CLI).
type TelegramSettings ¶ added in v0.12.0
type TelegramSettings struct {
// Token is the Telegram bot token from @BotFather
Token string `toml:"token,omitempty"`
// UserID is the authorized Telegram user ID from @userinfobot
UserID int64 `toml:"user_id,omitzero"`
}
TelegramSettings defines Telegram bot configuration for the conductor bridge
type TelegramValidatorInput ¶ added in v1.7.22
type TelegramValidatorInput struct {
// GlobalEnabled is the value of
// enabledPlugins."telegram@claude-plugins-official" in the relevant
// profile's settings.json, or false if that file or key is absent.
GlobalEnabled bool
// SessionChannels is the list of channels the session is launched with
// (Instance.Channels). Empty for ordinary child sessions.
SessionChannels []string
// SessionWrapper is the wrapper template for the session (may be empty).
SessionWrapper string
}
TelegramValidatorInput captures the three signals the validator inspects.
type TelegramWarning ¶ added in v1.7.22
type TelegramWarning struct {
Code string // GLOBAL_ANTIPATTERN | DOUBLE_LOAD | WRAPPER_DEPRECATED
Message string
}
TelegramWarning is one emission from the validator.
func ValidateTelegramTopology ¶ added in v1.7.22
func ValidateTelegramTopology(in TelegramValidatorInput) []TelegramWarning
ValidateTelegramTopology returns zero or more warnings for the given session configuration. Ordering is stable: GLOBAL_ANTIPATTERN, DOUBLE_LOAD, WRAPPER_DEPRECATED.
type TerminalSettings ¶ added in v1.7.72
type TerminalSettings struct {
// ITermBadge controls whether agent-deck sets the iTerm2 badge to the
// attached session's title for the duration of the attach, and refreshes
// it when Claude renames the session mid-attach. No-op outside iTerm2.
//
// AGENTDECK_ITERM_BADGE env var overrides this in either direction
// (=1/true/yes/on force on, =0/false/no/off force off; unset defers to
// this config). Caveat: env reliably reaches the attach/detach path
// (agent-deck reads its own env directly) but the rename-while-attached
// path runs in a hook subprocess spawned through agent-deck → tmux →
// Claude → hook, and Claude may filter custom env vars. For consistent
// behavior on both paths, prefer this config setting — every process
// re-reads it from disk, so propagation is independent of the spawn
// chain.
//
// Default: false (opt-in). Most users have their own iTerm2 badge scheme
// (e.g. host/cwd via shell PROMPT_COMMAND), so silently overwriting it on
// every attach is too presumptuous a default. Users who want the
// per-session badge set this to true explicitly.
ITermBadge *bool `toml:"iterm_badge,omitempty"`
}
TerminalSettings controls outer-terminal chrome agent-deck writes directly to the host terminal (bypassing tmux). These settings affect what the terminal emulator displays — currently only iTerm2's badge.
Example config.toml:
[terminal] iterm_badge = true
func GetTerminalSettings ¶ added in v1.7.72
func GetTerminalSettings() TerminalSettings
GetTerminalSettings returns terminal-chrome settings from config.
func (TerminalSettings) GetITermBadge ¶ added in v1.7.72
func (t TerminalSettings) GetITermBadge() bool
GetITermBadge returns whether the iTerm2 badge integration is enabled, defaulting to false (opt-in). Mirrors the GetInjectStatusLine pattern but with the inverse default — see ITermBadge field doc for rationale.
type TmuxSettings ¶ added in v0.12.1
type TmuxSettings struct {
// InjectStatusLine controls whether agent-deck injects a custom status line
// into new tmux sessions. When false, the tmux status bar is not modified,
// allowing users to use their own tmux status line configuration. This also
// disables Agent Deck's global tmux notification bar and key bindings so the
// runtime stops mutating global tmux options.
// Default: true (nil = use default true)
InjectStatusLine *bool `toml:"inject_status_line,omitempty"`
// Mouse controls whether agent-deck enables tmux mouse mode on new
// sessions. When false, tmux `mouse on` is never set, so the terminal
// emulator keeps raw control of mouse events — required by the VS Code
// Linux integrated terminal to let users click-drag to select text
// (issue #730). Affects both the inline set-option during session
// creation and the separate EnableMouseMode() path used on reconnect.
// Default: true (nil = use default true, preserves pre-#730 behavior)
Mouse *bool `toml:"mouse,omitempty"`
// LaunchInUserScope starts new tmux servers via `systemd-run --user --scope`
// so the tmux server lives under the user's systemd manager instead of the
// current login session scope. This keeps tmux alive when an SSH session
// scope is torn down.
//
// Default (when nil / field absent): true on Linux hosts where
// `systemd-run --user --version` succeeds, false otherwise. Explicit
// `launch_in_user_scope = true` or `launch_in_user_scope = false` in
// config.toml is always honored. Pointer type is required to distinguish
// "field absent" from "explicit false".
LaunchInUserScope *bool `toml:"launch_in_user_scope,omitempty"`
// LaunchAs selects the spawn form for new tmux servers (v1.7.21+).
// Valid values (case-insensitive, whitespace-trimmed):
// "scope" — systemd-run --user --scope (PR #467 legacy behavior)
// "service" — systemd-run --user --unit <NAME>.service with
// Type=forking + Restart=on-failure. Adds auto-restart
// if the tmux daemon dies unexpectedly (OOM, SIGKILL,
// kernel signal). Opt-in defense-in-depth.
// "direct" — plain `tmux new-session` (no systemd isolation).
// "auto" — service where systemd-user manager is available,
// else direct.
// "" — unset (default): defer to LaunchInUserScope.
//
// LaunchAs, when non-empty and valid, takes precedence over
// LaunchInUserScope. Unknown values are ignored (fall through to
// LaunchInUserScope) so a config typo doesn't silently opt the user
// onto an unintended spawn path.
//
// This is additive — v1.7.20 users get zero behavior change until
// they explicitly set launch_as.
LaunchAs *string `toml:"launch_as,omitempty"`
// WindowStyleOverride sets the tmux window-style (and window-active-style) for
// all sessions, overriding the theme default. Use "default" to let your terminal
// emulator's background show through instead of agent-deck's theme color.
// Empty string (default) means use the theme's built-in value.
// Takes precedence over the same keys in Options if both are set.
// Example: window_style_override = "default"
WindowStyleOverride string `toml:"window_style_override,omitempty"`
// ClearOnRestart clears the tmux scrollback buffer when a session is
// restarted (respawn-pane). When false (default), the previous session's
// output is preserved in scrollback. When true, scrollback is wiped so
// the new session starts with a clean buffer.
ClearOnRestart bool `toml:"clear_on_restart,omitempty"`
// DetachKey overrides the PTY-attach detach key (issue #434). Accepts
// the same lowercase "ctrl+<letter>" form as `[hotkeys].detach` (e.g.
// "ctrl+d"). When set to a non-empty string, it becomes an alias for
// `[hotkeys].detach`. Precedence: explicit `[hotkeys].detach` always
// wins; `[tmux].detach_key` is used only when `[hotkeys].detach` is
// absent. Empty string (default) preserves the built-in Ctrl+Q.
//
// Why the alias exists: #434 reporters asked for a `[tmux]` section
// entry because they think of the detach as a tmux-attach concern.
// Keeping `[hotkeys].detach` authoritative avoids two sources of truth.
DetachKey string `toml:"detach_key,omitempty"`
// Options is a map of tmux option names to values.
// These are passed to `tmux set-option -t <session>` after defaults.
Options map[string]string `toml:"options,omitempty"`
// SocketName is the tmux `-L <name>` socket selector for every
// agent-deck tmux spawn (v1.7.50+, issue #687). Empty string — the
// default — keeps pre-v1.7.50 behavior byte-for-byte: agent-deck shares
// the user's default tmux server at $TMUX_TMPDIR/tmux-<uid>/default.
//
// Set this to isolate agent-deck onto its own tmux server so:
// - `[tmux].inject_status_line`, bind-key, and global set-option
// mutations stay on the agent-deck server and never touch the
// user's interactive tmux config (the original #276 complaint);
// - a `tmux kill-server` in the user's shell can't take agent-deck's
// managed sessions down with it;
// - `tmux -L <name> ls` from the shell shows exactly agent-deck's
// sessions — no mixing with the user's own work sessions.
//
// Each Instance captures this value at creation time into
// Instance.TmuxSocketName; changing socket_name later does NOT migrate
// existing sessions (they remain reachable on their original socket
// until explicitly re-created). See docs/SOCKET_ISOLATION.md for the
// migration procedure.
//
// Precedence at Instance creation: CLI flag `--tmux-socket <name>`
// wins, else this config value, else empty.
SocketName string `toml:"socket_name,omitempty"`
}
TmuxSettings allows users to override tmux options applied to every session. Options are applied AFTER agent-deck's defaults, so they take precedence.
Example config.toml:
[tmux]
inject_status_line = false
options = { "allow-passthrough" = "all", "history-limit" = "50000" }
func GetTmuxSettings ¶ added in v0.12.1
func GetTmuxSettings() TmuxSettings
GetTmuxSettings returns tmux option overrides from config
func (TmuxSettings) GetInjectStatusLine ¶ added in v0.15.0
func (t TmuxSettings) GetInjectStatusLine() bool
GetInjectStatusLine returns whether to inject status line, defaulting to true.
func (TmuxSettings) GetLaunchAs ¶ added in v1.7.21
func (t TmuxSettings) GetLaunchAs() string
GetLaunchAs returns the canonicalised launch mode string parsed from config.toml's tmux.launch_as key. Returns "" if the field is unset or contains an unknown value (in which case downstream callers fall back to LaunchInUserScope). v1.7.21+.
func (TmuxSettings) GetLaunchInUserScope ¶ added in v1.3.1
func (t TmuxSettings) GetLaunchInUserScope() bool
GetLaunchInUserScope returns whether new tmux servers should be launched under the user's systemd manager. If LaunchInUserScope is non-nil (explicit override in config.toml), its value is returned. Otherwise the default is determined by isSystemdUserScopeAvailable(): true on Linux+systemd hosts, false elsewhere. PERSIST-01..PERSIST-03.
func (TmuxSettings) GetMouse ¶ added in v1.7.68
func (t TmuxSettings) GetMouse() bool
GetMouse returns whether tmux mouse mode should be enabled, defaulting to true. Issue #730: users on VS Code's Linux integrated terminal need mouse OFF so the terminal can handle click-drag selection natively.
func (TmuxSettings) GetSocketName ¶ added in v1.7.50
func (t TmuxSettings) GetSocketName() string
GetSocketName returns the trimmed `[tmux].socket_name` value, or "" when unset, whitespace-only, or absent. Centralising the trim here means every caller — tmux.SetDefaultSocketName at startup, CLI flag merging, Instance creation — sees the same sanitised value.
type ToolDef ¶ added in v0.3.0
type ToolDef struct {
// Command is the shell command to run
Command string `toml:"command,omitempty"`
// CompatibleWith opts this tool into compatibility behavior for a built-in
// tool even when the configured command is a wrapper script rather than the
// literal executable name. Supported values currently include "claude" and
// "codex".
CompatibleWith string `toml:"compatible_with,omitempty"`
// Wrapper is an optional command that wraps the tool command.
// Use {command} placeholder to include the tool command, or omit it to replace the command.
// Example: wrapper = "nvim +'terminal {command}' +'startinsert'"
Wrapper string `toml:"wrapper,omitempty"`
// Icon is the emoji/symbol to display
Icon string `toml:"icon,omitempty"`
// BusyPatterns are strings that indicate the tool is busy
BusyPatterns []string `toml:"busy_patterns,omitempty"`
// PromptPatterns are strings that indicate the tool is waiting for input
PromptPatterns []string `toml:"prompt_patterns,omitempty"`
// DetectPatterns are regex patterns to auto-detect this tool from terminal content
DetectPatterns []string `toml:"detect_patterns,omitempty"`
// ResumeFlag is the CLI flag to resume a session (e.g., "--resume")
ResumeFlag string `toml:"resume_flag,omitempty"`
// SessionIDEnv is the tmux environment variable name storing the session ID
SessionIDEnv string `toml:"session_id_env,omitempty"`
// DangerousMode enables dangerous mode flag for this tool
DangerousMode bool `toml:"dangerous_mode,omitempty"`
// DangerousFlag is the CLI flag for dangerous mode (e.g., "--dangerously-skip-permissions")
DangerousFlag string `toml:"dangerous_flag,omitempty"`
// OutputFormatFlag is the CLI flag for JSON output format (e.g., "--output-format json")
OutputFormatFlag string `toml:"output_format_flag,omitempty"`
// SessionIDJsonPath is the jq path to extract session ID from JSON output
SessionIDJsonPath string `toml:"session_id_json_path,omitempty"`
// EnvFile is a .env file specific to this tool
// Sourced AFTER global [shell].env_files
// Path can be absolute, ~ for home, $HOME/${VAR} for env vars, or relative to session working directory
EnvFile string `toml:"env_file,omitempty"`
// Env is inline environment variables for this tool
// These are exported AFTER env_file (highest priority)
// Example: env = { ANTHROPIC_BASE_URL = "https://...", API_KEY = "token" }
Env map[string]string `toml:"env,omitempty"`
// BusyPatternsExtra appends additional busy patterns to the built-in defaults
BusyPatternsExtra []string `toml:"busy_patterns_extra,omitempty"`
// PromptPatternsExtra appends additional prompt patterns to the built-in defaults
PromptPatternsExtra []string `toml:"prompt_patterns_extra,omitempty"`
// SpinnerChars replaces the default spinner characters entirely (use with caution)
SpinnerChars []string `toml:"spinner_chars,omitempty"`
// SpinnerCharsExtra appends additional spinner characters to the built-in defaults
SpinnerCharsExtra []string `toml:"spinner_chars_extra,omitempty"`
}
ToolDef defines a custom AI tool
func GetToolDef ¶ added in v0.3.0
GetToolDef returns a tool definition from user config Returns nil if tool is not defined
type ToolOptions ¶ added in v0.8.39
type ToolOptions interface {
// ToolName returns the name of the tool (e.g., "claude", "codex")
ToolName() string
// ToArgs returns command-line arguments for the tool
ToArgs() []string
}
ToolOptions is the interface for tool-specific launch options Each AI tool (claude, codex, gemini, etc.) can have its own options struct that implements this interface
type ToolOptionsWrapper ¶ added in v0.8.39
type ToolOptionsWrapper struct {
Tool string `json:"tool"`
Options json.RawMessage `json:"options"`
}
ToolOptionsWrapper wraps tool options for JSON serialization JSON structure: {"tool": "claude", "options": {...}}
type TransitionDaemon ¶ added in v0.19.13
type TransitionDaemon struct {
// contains filtered or unexported fields
}
func NewTransitionDaemon ¶ added in v0.19.13
func NewTransitionDaemon() *TransitionDaemon
func (*TransitionDaemon) Flush ¶ added in v1.7.45
func (d *TransitionDaemon) Flush()
Flush exposes the notifier's in-flight-dispatch wait for callers of SyncOnce that need deterministic log output before returning (e.g., the `agent-deck notify-daemon --once` CLI path).
func (*TransitionDaemon) ReplayUnackedCompletions ¶ added in v1.9.43
func (d *TransitionDaemon) ReplayUnackedCompletions(profile string)
ReplayUnackedCompletions re-delivers durable task-worker completion records (issue #1214) that have not yet been acknowledged — the wrapper wrote the record but no live parent was reachable to wake (conductor down/busy at exit). On a successful wake the record is acked so it never fires again. This is the restart-durability half of the kernel-exit mechanism: a completion that happened while the conductor was offline is delivered exactly once when it returns, with no double-wake.
type TransitionNotificationEvent ¶ added in v0.19.13
type TransitionNotificationEvent struct {
ChildSessionID string `json:"child_session_id"`
ChildTitle string `json:"child_title"`
Profile string `json:"profile"`
FromStatus string `json:"from_status"`
ToStatus string `json:"to_status"`
Timestamp time.Time `json:"timestamp"`
// Substate is the additive Honest-Status-v2 refinement of ToStatus
// (model-unavailable, auth-401, idle-at-empty-prompt, running). It makes
// each status-transition event structured and substate-bearing so fleet
// telemetry (a future operational-KG sink) can distinguish a dead-model
// no-op loop from a genuinely-running session. Empty when no refinement
// applies. Observability hook only — does not affect delivery/dedup.
Substate string `json:"substate,omitempty"`
// LastOutputHash is a cheap stable signal (e.g. SHA-1 of the last N
// bytes of the child's tmux pane at transition time) used by the
// notifier's #1142 dedup to suppress repeated [EVENT] notifications
// for a dormant child whose pane content hasn't changed. Optional —
// empty string disables hash-based dedup and falls back to the legacy
// 90s short window.
LastOutputHash string `json:"last_output_hash,omitempty"`
TargetSessionID string `json:"target_session_id,omitempty"`
TargetKind string `json:"target_kind,omitempty"` // parent | conductor
DeliveryResult string `json:"delivery_result,omitempty"`
// Kind distinguishes a finished event (issue #1186) from the default
// status-transition event. Empty means the legacy transition event
// ("[EVENT] Child is waiting"); transitionKindFinished means a
// worker-asserted completion ("[DONE] Child finished: status=…").
Kind string `json:"kind,omitempty"`
// DoneStatus/DoneSummary carry the parsed completion sentinel for a
// finished event. Unused for transition events.
DoneStatus string `json:"done_status,omitempty"`
DoneSummary string `json:"done_summary,omitempty"`
// TurnFingerprint identifies a child's completed turn for exactly-once
// consumer effects (issue #1225). Coarser than EventFingerprint (which
// keys on emit-instant so retries collapse): two emits of the SAME turn
// share a TurnFingerprint, so a parent that drains the durable outbox acts
// on the turn exactly once even across a daemon restart that re-stamps
// Timestamp. Format: "<child_id>@<turn-signal-hash>".
TurnFingerprint string `json:"turn_fingerprint,omitempty"`
// Attempts counts producer commit attempts against an unresolvable target
// before the record is moved to the dead-letter store (issue #1225). Bounds
// the old dropped_no_target ~1/sec runaway to a terminal state.
Attempts int `json:"attempts,omitempty"`
// DeadLetterReason records WHY a record was terminally undeliverable (audit
// B5): orphan, child_removed, parent_removed (incl. cross-profile),
// no_notify, self_conductor, or unresolvable. Empty for delivered records.
// Flows to the dead-letter record and the operator-visible missed-log line so
// a misconfiguration is distinguishable from a benign suppression.
DeadLetterReason string `json:"dead_letter_reason,omitempty"`
}
func DrainInboxForParent ¶ added in v1.9.44
func DrainInboxForParent(parentID string) ([]TransitionNotificationEvent, error)
DrainInboxForParent drains the parent's durable outbox and returns the deliverables (last-wins per child, exactly-once per turn). See file comment.
Two-phase, crash-safe (audit B1): stage the records into the in-flight WAL before truncating the inbox, then finalize the consumed ledger and drop the WAL. A crash between the two phases re-delivers from the WAL on the next call.
func DrainStagePhaseForCrashTest ¶ added in v1.9.44
func DrainStagePhaseForCrashTest(parentID string) ([]TransitionNotificationEvent, error)
DrainStagePhaseForCrashTest runs ONLY phase 1 of the drain (stage + truncate) and returns the staged records, simulating a process that dies before the consumed ledger is finalized. Tests use it to prove the at-least-once re-delivery contract (audit B1). Production code never calls it.
func ReadAndTruncateInbox ¶ added in v1.7.73
func ReadAndTruncateInbox(parentSessionID string) ([]TransitionNotificationEvent, error)
ReadAndTruncateInbox reads all events from the parent's inbox and removes the file. Returns an empty slice (not an error) when the inbox doesn't exist or holds no parseable lines.
This is the LEGACY raw at-most-once path (exposed as `agent-deck inbox <id>` for ad-hoc inspection). The read+remove pair IS atomic against concurrent WriteInboxEvent/CommitToInbox — inboxWriteMu is held across Open→Remove, so a producer cannot interleave a write between them. What it does NOT provide is crash durability: a process death after the file is removed but before the caller acts loses the records. For the guaranteed at-least-once-with-dedup contract use DrainInboxForParent / DrainForStopHook (inbox_consumer.go), which stage to an in-flight WAL before truncating.
func ReadDeadLetter ¶ added in v1.9.44
func ReadDeadLetter(childSessionID string) ([]TransitionNotificationEvent, error)
ReadDeadLetter returns the dead-lettered records for a child (empty if none).
Audit B3: corrupt lines are SKIPPED (matching ReadAndTruncateInbox), not fatal. Dead-letter is the operator's last-resort forensic trail; one garbled/truncated line must not hide every valid record after it. The scanner uses the raised line cap so a fat (capped) summary still reads back.
type TransitionNotifier ¶ added in v0.19.13
type TransitionNotifier struct {
// contains filtered or unexported fields
}
func NewTransitionNotifier ¶ added in v0.19.13
func NewTransitionNotifier() *TransitionNotifier
func (*TransitionNotifier) Close ¶ added in v1.7.73
func (n *TransitionNotifier) Close()
Close is retained for API compatibility with callers that defer cleanup of a notifier. Issue #1225 made delivery a synchronous commit to the durable outbox, so there are no background goroutines to cancel; Close is a no-op. Idempotent.
func (*TransitionNotifier) DeliverCompletion ¶ added in v1.9.43
func (n *TransitionNotifier) DeliverCompletion(rec CompletionRecord) bool
DeliverCompletion wakes the child's parent with a [DONE] event and reports whether the wake was accepted into the delivery system (sent, or deferred to the retry queue/inbox for a busy parent). A dropped/failed result — e.g. the parent (conductor) is down or unresolvable — returns false so the durable record stays unacked for replay. Pending (unfinished) records are ignored. DeliverCompletion commits a finished one-shot worker's completion to the parent's durable per-parent outbox (issue #1225). It returns true ONLY when the record durably landed in the inbox — i.e. it is safe to ack the durable completion record. It returns false when the parent was unresolvable or a transient error occurred, so the completion record stays unacked and the daemon's replay re-attempts it (and dead-letters after a bounded budget).
This removes the old ack-on-defer bug (taskworker.go:301): a record is no longer acked when it merely reached a volatile in-process retry queue; it is acked only once provably committed to the durable inbox.
func (*TransitionNotifier) Flush ¶ added in v1.7.45
func (n *TransitionNotifier) Flush()
Flush is retained for API compatibility with the bounded-lifetime callers (`notify-daemon --once`, graceful shutdown, deterministic tests). Issue #1225 made delivery a synchronous commit to the durable outbox, so there is nothing in flight to wait for; Flush is a no-op.
func (*TransitionNotifier) NotifyFinished ¶ added in v1.9.36
func (n *TransitionNotifier) NotifyFinished(event TransitionNotificationEvent) TransitionNotificationEvent
NotifyFinished commits a worker-asserted completion event (issue #1186) to the child's parent's durable outbox. Unlike NotifyTransition it is not gated by ShouldNotifyTransition (a finished event has no from→to transition); per-task idempotency is the daemon's responsibility (it only calls this when the detected sentinel actually changed).
func (*TransitionNotifier) NotifyTransition ¶ added in v0.19.13
func (n *TransitionNotifier) NotifyTransition(event TransitionNotificationEvent) TransitionNotificationEvent
NotifyTransition validates the event and commits it to the parent's durable outbox (issue #1225: PULL, not push). A busy conductor drains it at its own turn boundary, so delivery no longer depends on an idle window that never opens. Synchronous returns: committed_inbox / dropped_no_target / failed.
type UISettings ¶ added in v1.9.23
type UISettings struct {
// PreviewPct is the percentage of horizontal width allocated to the
// preview pane (sessions list gets the remainder). Valid range: 10-90.
// Default: 65 (current behavior — sessions 35 / preview 65).
// Adjustable at runtime via < and > keybindings (5% step).
PreviewPct int `toml:"preview_pct,omitzero"`
// PreviewOrientation controls where the PREVIEW pane sits relative to
// the SESSIONS list on wide terminals (>= 80 cols). "right" (default)
// keeps the historical side-by-side split; "below" stacks PREVIEW under
// SESSIONS (useful on tall/portrait monitors). Narrow terminals always
// stack regardless. Toggle at runtime with the `O` keybinding.
PreviewOrientation string `toml:"preview_orientation,omitempty"`
// ITermOpenAs controls whether Shift+Enter pops the focused session
// into a new iTerm2 *tab* or a new iTerm2 *window* on macOS. Valid
// values: "tab", "window". Empty defaults to "tab" (iTerm's natural
// UX). Issue #1100, follow-up to #1098 — credit @ddorman-dn.
ITermOpenAs string `toml:"iterm_open_as,omitempty"`
// ShellSplit controls the terminal used by the open_shell_here hotkey.
// Valid values:
// "iterm" — always open an iTerm2 vertical split pane (macOS only)
// "tmux" — always open a new tmux window
// "" — auto: use iTerm2 split when LC_TERMINAL=iTerm2 or
// TERM_PROGRAM=iTerm.app, otherwise tmux
// Default: "" (auto). Issue #1470.
ShellSplit string `toml:"shell_split,omitempty"`
// RemoteLatencyRefreshSecs sets how often the TUI re-measures the
// round-trip latency to each configured remote (issue #1103). Valid
// range: 2-300. Default: matches [system_stats].refresh_seconds (5s)
// so the latency marker ticks alongside CPU/RAM/load.
RemoteLatencyRefreshSecs int `toml:"remote_latency_refresh_secs,omitzero"`
// RemoteSessionRefreshSecs sets how often the TUI re-fetches the remote
// session list over SSH (issue #1170). Remote sessions created after the
// TUI launched were invisible until quit+relaunch; this is the poll
// cadence that reconciles the list. Valid range: 5-300. Default: 15s,
// tightening the visibility latency reported on v1.9.30.
RemoteSessionRefreshSecs int `toml:"remote_session_refresh_secs,omitzero"`
// ShowOnlyInstalledTools, when true, hides tools from the new-session
// dialogs (TUI + web) whose command does not resolve on the host PATH
// (issue #1259). Default false: no PATH probing happens and the dialogs are
// byte-identical to before. shell is always shown; if nothing else resolves
// the dialog falls back to showing all tools plus a one-line hint. This is a
// display filter only — `agent-deck launch -c <tool>` still spawns a hidden
// tool.
ShowOnlyInstalledTools bool `toml:"show_only_installed_tools,omitempty"`
// HiddenTools lists tool names to hide from the new-session picker (TUI + web).
// Denylist: absent or empty shows every tool (subject to show_only_installed_tools).
// shell is always shown and cannot be hidden.
HiddenTools []string `toml:"hidden_tools,omitempty"`
// "full" (default) — the historic verbose bar: filled key chips,
// width-adaptive, advertising every action. This is
// today's behavior and stays the default so the look
// never changes without an explicit opt-in.
// "curated" — lighter, dim inline text advertising only the
// actions relevant to the selected row, with the
// settings and help keys always last (opt-in).
// "compact" — force the abbreviated chip tier regardless of width.
// "minimal" — force the keys-only tier regardless of width.
// Empty or unknown values fall back to "full". This is purely a
// rendering preference (TUI UX initiative, item 1): no keybinding is
// added, removed, or changed — only what the footer advertises. Every
// action remains reachable by its key and is fully listed under help (?).
Footer string `toml:"footer,omitempty"`
// NewSessionEnterAdvances controls what Enter does on the free-text
// Name/Branch fields of the new-session dialog. As of the UX top-3 pass this
// is ON BY DEFAULT (the mechanism shipped opt-in in PR #1295): Enter advances
// focus to the next field, so typing a name and pressing Enter no longer
// silently creates a session with all defaults — the #1 reported new-session
// trap. Ctrl+S is the explicit submit shortcut and submits in BOTH modes;
// Enter still submits from non-text rows (tool/checkboxes). The pointer lets
// us distinguish "unset" (nil → default true) from an explicit opt-OUT
// (`new_session_enter_advances = false` → restores the legacy Enter-submits
// behavior). Set `= true` (or leave unset) to keep the new default.
NewSessionEnterAdvances *bool `toml:"new_session_enter_advances"`
// AttachOnCreate controls whether creating a session in the TUI (the `n`
// new-session dialog) immediately attaches to the new session's pane
// instead of only moving the cursor to it. Default false: creating a
// session selects it (today's behavior) and the user presses Enter to
// attach. Set `= true` to "instantly open" each new session. CLI
// `add`/`session start` are unaffected by this flag — they attach only
// with an explicit `--attach`.
AttachOnCreate bool `toml:"attach_on_create,omitempty"`
}
UISettings controls TUI layout proportions. See issue #1092.
func (UISettings) GetAttachOnCreate ¶ added in v1.10.9
func (u UISettings) GetAttachOnCreate() bool
GetAttachOnCreate reports whether the TUI should attach to a newly created session immediately instead of only selecting it. Default false.
func (UISettings) GetFooter ¶ added in v1.9.49
func (u UISettings) GetFooter() string
GetFooter returns the configured footer style, normalized to one of the known values. Empty or unknown input falls back to DefaultFooter ("full"). Matching is case-insensitive so users may write "Full" or "MINIMAL" in TOML.
func (UISettings) GetITermOpenAs ¶ added in v1.9.24
func (u UISettings) GetITermOpenAs() string
GetITermOpenAs returns the configured iTerm open mode. Unknown or empty values fall through to the default ("tab"). Matching is case-insensitive so users can write "Tab" or "WINDOW" in TOML.
func (UISettings) GetNewSessionEnterAdvances ¶ added in v1.9.49
func (u UISettings) GetNewSessionEnterAdvances() bool
GetNewSessionEnterAdvances reports whether Enter on the new-session dialog's free-text Name/Branch fields should advance focus (true) instead of submitting the form (false). Defaults to true when unset: Enter-advances is the default so typing a name + Enter no longer silently submits with all defaults. A literal `new_session_enter_advances = false` opts out and restores the legacy Enter-submits behavior. Ctrl+S submits in both modes.
func (UISettings) GetPreviewOrientation ¶ added in v1.10.9
func (u UISettings) GetPreviewOrientation() string
GetPreviewOrientation returns the configured preview-pane orientation for wide terminals. Unknown or empty values fall through to the default ("right"). Matching is case-insensitive so users can write "Below" or "RIGHT" in TOML.
func (UISettings) GetPreviewPct ¶ added in v1.9.23
func (u UISettings) GetPreviewPct() int
GetPreviewPct returns the configured preview percentage, clamped to [MinPreviewPct, MaxPreviewPct]. Falls back to DefaultPreviewPct when unset or out of range.
func (UISettings) GetRemoteLatencyRefreshSecs ¶ added in v1.9.24
func (u UISettings) GetRemoteLatencyRefreshSecs(fallbackSecs int) int
GetRemoteLatencyRefreshSecs returns the remote latency refresh interval in seconds, clamped to [2, 300]. When the user has not set this value it falls back to fallbackSecs (typically the system_stats refresh interval, so the latency marker ticks at the same cadence as CPU/RAM per #1103). fallbackSecs <= 0 maps to 5.
func (UISettings) GetRemoteSessionRefreshSecs ¶ added in v1.9.32
func (u UISettings) GetRemoteSessionRefreshSecs() int
GetRemoteSessionRefreshSecs returns the remote session-list poll interval in seconds, clamped to [MinRemoteSessionRefreshSecs, MaxRemoteSessionRefreshSecs]. Unset (<= 0) falls back to DefaultRemoteSessionRefreshSecs. See issue #1170.
func (UISettings) GetShellSplit ¶ added in v1.10.9
func (u UISettings) GetShellSplit() string
GetShellSplit returns the configured shell-split mode. Unknown or empty values return "" (auto-detect). Matching is case-insensitive.
type UpdateSettings ¶ added in v0.8.2
type UpdateSettings struct {
// AutoUpdate automatically installs updates without prompting
// Default: false
AutoUpdate bool `toml:"auto_update,omitempty"`
// CheckEnabled enables automatic update checks on startup
// Default: true (nil = true)
CheckEnabled *bool `toml:"check_enabled,omitempty"`
// CheckIntervalHours is how often to check for updates (in hours)
// Default: 24
CheckIntervalHours int `toml:"check_interval_hours,omitzero"`
// NotifyInCLI shows update notification in CLI commands (not just TUI)
// Default: true (nil = true)
NotifyInCLI *bool `toml:"notify_in_cli,omitempty"`
}
UpdateSettings defines auto-update configuration
func GetUpdateSettings ¶ added in v0.8.2
func GetUpdateSettings() UpdateSettings
GetUpdateSettings returns update settings with defaults applied
func (UpdateSettings) GetCheckEnabled ¶ added in v1.9.61
func (u UpdateSettings) GetCheckEnabled() bool
GetCheckEnabled returns whether update checks are enabled (default: true).
func (UpdateSettings) GetNotifyInCLI ¶ added in v1.9.61
func (u UpdateSettings) GetNotifyInCLI() bool
GetNotifyInCLI returns whether CLI update notifications are enabled (default: true).
type UserConfig ¶ added in v0.3.0
type UserConfig struct {
// DefaultTool is the pre-selected AI tool when creating new sessions
// Valid values: "claude", "gemini", "opencode", "codex", "pi", or any custom tool name
// If empty or invalid, defaults to "shell" (no pre-selection)
DefaultTool string `toml:"default_tool,omitempty"`
// DefaultPath is the global fallback project directory for `agent-deck add`
// when no explicit path or group default_path is provided.
DefaultPath string `toml:"default_path,omitempty"`
// Hotkeys overrides default keyboard shortcuts in the TUI.
// Keys are action names, values are key bindings (e.g., "delete" = "backspace").
// Set an action to "" to explicitly unbind it.
Hotkeys map[string]string `toml:"hotkeys,omitempty"`
// Theme sets the color scheme: "dark" (default), "light", or "system"
Theme string `toml:"theme,omitempty"`
// Tools defines custom AI tool configurations
Tools map[string]ToolDef `toml:"tools,omitempty"`
// MCPDefaultScope sets the default scope for MCP operations
// Valid values: "local" (default), "global", "user"
MCPDefaultScope string `toml:"mcp_default_scope,omitempty"`
// ManageMCPJson controls whether agent-deck writes to .mcp.json in project directories.
// Set to false to prevent agent-deck from touching any .mcp.json files, which is useful
// when you manage that file manually or via another tool.
// Default: true (nil = true)
ManageMCPJson *bool `toml:"manage_mcp_json,omitempty"`
// SyncTitle controls whether agent-deck overwrites a session's Title with the
// agent's own session-name (e.g. Claude's `--name` / `/rename`, issues #572/#697).
// Tool-agnostic, global switch. Set false to keep the title you gave the session.
// The per-session TitleLocked flag remains available as a finer-grained override.
// Default: true (nil = true)
SyncTitle *bool `toml:"sync_title,omitempty"`
// GroupSort controls the order of sessions within a group.
// "creation" (default) — fixed creation order; honors K/J manual reorder.
// "actionable" — issue #857 status→recency→Order surfacing.
// Empty or unrecognized values normalize to "creation".
GroupSort string `toml:"group_sort,omitempty"`
// MCPs defines available MCP servers for the MCP Manager
// These can be attached/detached per-project via the MCP Manager (M key)
MCPs map[string]MCPDef `toml:"mcps,omitempty"`
// Plugins defines available Claude Code plugins for per-session attach
// (RFC docs/rfc/PLUGIN_ATTACH.md). Catalog-only in v1: every name passed
// via `--plugin <name>` must resolve to an entry here. Each entry maps a
// short catalog name (e.g. "octopus") to a Claude Code plugin id
// (`<name>@<source>`) plus per-plugin policy (auto-install, channel link).
Plugins map[string]PluginDef `toml:"plugins,omitempty"`
// Claude defines Claude Code integration settings
Claude ClaudeSettings `toml:"claude,omitempty"`
// Profiles defines optional per-profile overrides.
// Example:
// [profiles.work.claude]
// config_dir = "~/.claude-work"
Profiles map[string]ProfileSettings `toml:"profiles,omitempty"`
// Groups defines optional per-group overrides.
// Example:
// [groups."my-group".claude]
// config_dir = "~/.claude-my-group"
Groups map[string]GroupSettings `toml:"groups,omitempty"`
// GroupDefaults holds defaults applied to NEWLY-created groups only.
// Existing groups (loaded from state.db) are never affected.
GroupDefaults GroupDefaultsSettings `toml:"group_defaults,omitempty"`
// Conductors defines optional per-conductor overrides.
// Keyed by conductor name (matches Instance.Title minus "conductor-" prefix).
// Mirrors Groups — see ConductorOverrides for the sub-table shape.
// Closes issue #602.
// Example:
// [conductors.gsd-v154.claude]
// config_dir = "~/.claude-work"
// env_file = "~/git/work/.envrc"
Conductors map[string]ConductorOverrides `toml:"conductors,omitempty"`
// Gemini defines Gemini CLI integration settings
Gemini GeminiSettings `toml:"gemini,omitempty"`
// OpenCode defines OpenCode CLI integration settings
OpenCode OpenCodeSettings `toml:"opencode,omitempty"`
// Codex defines Codex CLI integration settings
Codex CodexSettings `toml:"codex,omitempty"`
// Copilot defines GitHub Copilot CLI integration settings (Issue #556)
Copilot CopilotSettings `toml:"copilot,omitempty"`
// Crush defines charmbracelet/crush CLI integration settings (Issue #940)
Crush CrushSettings `toml:"crush,omitempty"`
// Hermes defines Hermes Agent CLI integration settings
Hermes HermesSettings `toml:"hermes,omitempty"`
// Worktree defines git worktree preferences
Worktree WorktreeSettings `toml:"worktree,omitempty"`
// GlobalSearch defines global conversation search settings
GlobalSearch GlobalSearchSettings `toml:"global_search,omitempty"`
// Logs defines session log management settings
Logs LogSettings `toml:"logs,omitempty"`
// MCPPool defines HTTP MCP pool settings for shared MCP servers
MCPPool MCPPoolSettings `toml:"mcp_pool,omitempty"`
// Updates defines auto-update settings
Updates UpdateSettings `toml:"updates,omitempty"`
// Preview defines preview pane display settings
Preview PreviewSettings `toml:"preview,omitempty"`
// Experiments defines experiment folder settings for 'try' command
Experiments ExperimentsSettings `toml:"experiments,omitempty"`
// Notifications defines waiting session notification bar settings
Notifications NotificationsConfig `toml:"notifications,omitempty"`
// Instances defines multiple instance behavior settings
Instances InstanceSettings `toml:"instances,omitempty"`
// Shell defines global shell environment settings for sessions
Shell ShellSettings `toml:"shell,omitempty"`
// Maintenance defines automatic maintenance worker settings
Maintenance MaintenanceSettings `toml:"maintenance,omitempty"`
// Status defines session status detection settings
Status StatusSettings `toml:"status,omitempty"`
// Conductor defines conductor (meta-agent orchestration) settings
Conductor ConductorSettings `toml:"conductor,omitempty"`
// Tmux defines tmux option overrides applied to every session
Tmux TmuxSettings `toml:"tmux,omitempty"`
// Docker defines Docker sandbox settings for containerized sessions
Docker DockerSettings `toml:"docker,omitempty"`
// Fork defines quick-fork (f) and fork-dialog (Shift+F) default behavior.
Fork ForkSettings `toml:"fork,omitempty"`
// Remotes defines named SSH remote agent-deck instances
Remotes map[string]RemoteConfig `toml:"remotes,omitempty"`
// OpenClaw defines OpenClaw gateway integration settings
OpenClaw OpenClawSettings `toml:"openclaw,omitempty"`
// Display defines rendering and display settings
Display DisplaySettings `toml:"display,omitempty"`
// Costs defines cost tracking and budget settings
Costs CostsSettings `toml:"costs,omitempty"`
// SystemStats defines system stats display settings (CPU, RAM, etc.)
SystemStats SystemStatsSettings `toml:"system_stats,omitempty"`
// Watcher defines event watcher settings
Watcher WatcherSettings `toml:"watcher,omitempty"`
// Feedback defines in-product feedback prompt settings (v1.7.38+).
// Mirrors the opt-out in ~/.agent-deck/feedback-state.json so it is visible
// to the user and editable without running `agent-deck feedback`.
Feedback FeedbackSettings `toml:"feedback,omitempty"`
// Terminal defines outer-terminal chrome settings — sequences agent-deck
// writes directly to the host terminal (iTerm2 badge, etc), distinct
// from anything tmux draws. Empty/absent uses defaults; see TerminalSettings.
Terminal TerminalSettings `toml:"terminal,omitempty"`
// Web defines `agent-deck web` HTTP server settings.
Web WebSettings `toml:"web,omitempty"`
// UI defines TUI layout settings (split ratios, etc).
UI UISettings `toml:"ui,omitempty"`
// SelfHeal defines self-heal supervision settings (SELF-HEAL-DESIGN.md).
// Stage 1 (v1.9.67) is observe-only: it logs what it WOULD do, takes no
// action. See SelfHealSettings.
SelfHeal SelfHealSettings `toml:"selfheal,omitempty"`
// Performance holds opt-in resource tuning for multi-instance setups.
Performance PerformanceSettings `toml:"performance,omitempty"`
}
UserConfig represents user-facing configuration in TOML format.
TOML serialization: every field must use omitempty (string/bool/slice/map/pointer) or omitzero (int/struct) so zero-value fields are not written to disk. Without this, SaveUserConfig bloats the file with sections the user never configured. TestSaveUserConfig_ZeroValueConfigProducesNoSections enforces this invariant.
func LoadUserConfig ¶ added in v0.3.0
func LoadUserConfig() (*UserConfig, error)
LoadUserConfig loads the user configuration from TOML file. After the first load the result is cached; the cache is invalidated when config.toml's mtime advances, so external edits to the file (e.g. the user editing ~/.agent-deck/config.toml by hand while the TUI is running) are picked up on the next call without a manual ClearUserConfigCache.
func MergePanelConfigOntoDisk ¶ added in v1.9.21
func MergePanelConfigOntoDisk(panel *UserConfig) (*UserConfig, error)
MergePanelConfigOntoDisk loads the on-disk UserConfig and overlays the subset of fields that the TUI settings panel and setup wizard manage. Every other top-level field — Remotes, Hotkeys, Plugins, Conductors, Groups, Notifications, OpenClaw, Costs, Watcher, Shell, etc. — is preserved verbatim from disk.
Issue #1067 fix. Previously, SettingsPanel.GetConfig and SetupWizard.GetConfig built fresh UserConfig values and home.go saved them directly. Any top-level field the panel did not explicitly copy from originalConfig was silently wiped — the reporter saw this as "remotes disappeared after Ctrl+C exit" because the most common path that triggers the save is opening Settings during a session.
The fix inverts the data flow: instead of "construct fresh + manually preserve a few fields", we "start from disk + overlay panel-managed fields". New top-level UserConfig fields are now safe-by-default — they survive panel saves unless explicitly listed here.
Callers (home.go settings + setup wizard paths) should replace
if err := SaveUserConfig(panel.GetConfig()); err != nil { ... }
with
merged, err := session.MergePanelConfigOntoDisk(panel.GetConfig())
if err == nil { _ = session.SaveUserConfig(merged) }
to inherit the preservation guarantee.
func ReloadUserConfig ¶ added in v0.3.0
func ReloadUserConfig() (*UserConfig, error)
ReloadUserConfig forces a reload of the user config
func (*UserConfig) ClaimPollingEnabled ¶ added in v1.10.9
func (c *UserConfig) ClaimPollingEnabled() bool
ClaimPollingEnabled reports whether claim-based polling is enabled.
func (*UserConfig) GetConductorClaudeCommand ¶ added in v1.10.9
func (c *UserConfig) GetConductorClaudeCommand(name string) string
GetConductorClaudeCommand returns the conductor-specific Claude command, if configured. Mirrors GetGroupClaudeCommand; conductor beats group in the resolution chain (CFG-08 precedence).
func (*UserConfig) GetConductorClaudeConfigDir ¶ added in v1.5.4
func (c *UserConfig) GetConductorClaudeConfigDir(name string) string
GetConductorClaudeConfigDir returns the conductor-specific Claude config directory, if configured. Keyed by conductor name (Instance.Title minus "conductor-" prefix — single source of truth is conductorNameFromInstance in claude.go). Path expansion matches GetGroupClaudeConfigDir. Returns "" when the conductor has no block or no config_dir — callers fall through to the group/profile/global chain.
func (*UserConfig) GetConductorClaudeEnv ¶ added in v1.10.9
func (c *UserConfig) GetConductorClaudeEnv(name string) map[string]string
GetConductorClaudeEnv returns the conductor-specific inline env map, if configured. Applied over the group env map at spawn (conductor wins per key). Nil when the conductor has no block or no env.
func (*UserConfig) GetConductorClaudeEnvFile ¶ added in v1.5.4
func (c *UserConfig) GetConductorClaudeEnvFile(name string) string
GetConductorClaudeEnvFile returns the conductor-specific Claude env_file, if configured. Mirrors GetGroupClaudeEnvFile — no expansion here; resolvePath handles it at the spawn-command build site (env.go).
func (*UserConfig) GetConductorClaudeMCPs ¶ added in v1.10.9
func (c *UserConfig) GetConductorClaudeMCPs(name string) []string
GetConductorClaudeMCPs returns the conductor-specific [mcps.X] catalog names, if configured. Same floor semantics as GetConductorClaudePlugins.
func (*UserConfig) GetConductorClaudeModel ¶ added in v1.10.9
func (c *UserConfig) GetConductorClaudeModel(name string) string
GetConductorClaudeModel returns the conductor-specific Claude model default, if configured. Mirrors GetGroupClaudeModel.
func (*UserConfig) GetConductorClaudePlugins ¶ added in v1.10.9
func (c *UserConfig) GetConductorClaudePlugins(name string) []string
GetConductorClaudePlugins returns conductor-specific catalog plugin keys.
func (*UserConfig) GetConductorClaudeSkills ¶ added in v1.10.9
func (c *UserConfig) GetConductorClaudeSkills(name string) []string
GetConductorClaudeSkills returns the conductor-specific skill-loadout entries, if configured. The effective loadout for a conductor session is the union of its group chain's skills and this list (floor semantics).
func (*UserConfig) GetConductorHermesEnvFile ¶ added in v1.9.47
func (c *UserConfig) GetConductorHermesEnvFile(name string) string
GetConductorHermesEnvFile returns the conductor-specific Hermes env_file, if configured. Mirrors GetConductorClaudeEnvFile.
func (*UserConfig) GetGroupClaudeCommand ¶ added in v1.10.9
func (c *UserConfig) GetGroupClaudeCommand(groupPath string) string
GetGroupClaudeCommand returns the group-specific Claude command, walking ancestor groups when the exact path has no override. No path expansion — the value is a command/alias, not a filesystem path.
func (*UserConfig) GetGroupClaudeConfigDir ¶ added in v1.5.4
func (c *UserConfig) GetGroupClaudeConfigDir(groupPath string) string
GetGroupClaudeConfigDir returns the group-specific Claude config directory, walking ancestor groups when the exact path has no override. A child group like "personal/foo" inherits the [groups."personal".claude].config_dir setting from its parent so per-group account isolation propagates through nested groups.
func (*UserConfig) GetGroupClaudeEnv ¶ added in v1.10.9
func (c *UserConfig) GetGroupClaudeEnv(groupPath string) map[string]string
GetGroupClaudeEnv returns the merged inline env map for a group. Unlike the scalar keys (nearest ancestor wins wholesale), env maps merge along the ancestor chain per key — applied root-first so the nearest group's value wins on conflict while parent-only keys persist. A child group adding one variable must not silently drop the parent's map. Returns a freshly allocated map (callers may overlay onto it), nil when no level defines env.
func (*UserConfig) GetGroupClaudeEnvFile ¶ added in v1.5.4
func (c *UserConfig) GetGroupClaudeEnvFile(groupPath string) string
GetGroupClaudeEnvFile returns the group-specific Claude env file, walking ancestor groups when the exact path has no override. Mirrors GetGroupClaudeConfigDir's inheritance semantics so nested groups don't silently drop the parent's env_file.
func (*UserConfig) GetGroupClaudeMCPs ¶ added in v1.10.9
func (c *UserConfig) GetGroupClaudeMCPs(groupPath string) []string
GetGroupClaudeMCPs returns the union of [mcps.X] catalog names along the group ancestor chain, deduplicated, root-first. Same floor semantics as GetGroupClaudePlugins.
func (*UserConfig) GetGroupClaudeModel ¶ added in v1.10.9
func (c *UserConfig) GetGroupClaudeModel(groupPath string) string
GetGroupClaudeModel returns the group-specific Claude model default, walking ancestor groups when the exact path has no override.
func (*UserConfig) GetGroupClaudePlugins ¶ added in v1.10.9
func (c *UserConfig) GetGroupClaudePlugins(groupPath string) []string
GetGroupClaudePlugins returns the union of catalog plugin keys along the group ancestor chain, deduplicated and root-first.
func (*UserConfig) GetGroupClaudeSkills ¶ added in v1.10.9
func (c *UserConfig) GetGroupClaudeSkills(groupPath string) []string
GetGroupClaudeSkills returns the union of skill-loadout entries along the group ancestor chain, deduplicated, root-first. Union (not nearest-wins) because the loadout is an attach-only floor: a child group declaring its own skills adds to the parent's floor rather than replacing it.
func (*UserConfig) GetGroupHermesEnvFile ¶ added in v1.9.47
func (c *UserConfig) GetGroupHermesEnvFile(groupPath string) string
GetGroupHermesEnvFile returns the group-specific Hermes env file, walking ancestor groups when the exact path has no override. Mirrors GetGroupClaudeEnvFile's inheritance semantics.
func (*UserConfig) GetGroupSort ¶ added in v1.9.73
func (c *UserConfig) GetGroupSort() string
GetGroupSort returns the normalized within-group sort mode: "actionable" only when explicitly set, otherwise "creation" (the default).
func (*UserConfig) GetProfileClaudeConfigDir ¶ added in v0.19.1
func (c *UserConfig) GetProfileClaudeConfigDir(profile string) string
GetProfileClaudeConfigDir returns the profile-specific Claude config directory, if configured.
func (*UserConfig) GetProfileCodexConfigDir ¶ added in v1.9.18
func (c *UserConfig) GetProfileCodexConfigDir(profile string) string
GetProfileCodexConfigDir returns the profile-specific Codex config directory, if configured.
func (*UserConfig) GetShowAnalytics ¶ added in v0.8.27
func (c *UserConfig) GetShowAnalytics() bool
GetShowAnalytics returns whether to show analytics panel, defaulting to false
func (*UserConfig) GetShowNotes ¶ added in v0.25.0
func (c *UserConfig) GetShowNotes() bool
GetShowNotes returns whether to show notes section, defaulting to false
func (*UserConfig) GetShowOutput ¶ added in v0.8.27
func (c *UserConfig) GetShowOutput() bool
GetShowOutput returns whether to show terminal output in preview
func (*UserConfig) GetSyncTitle ¶ added in v1.9.47
func (c *UserConfig) GetSyncTitle() bool
GetSyncTitle returns whether agent-deck may overwrite a session Title with the agent's own session-name. Tool-agnostic. Defaults to true (nil = true) so existing installs keep the current behavior; set sync_title = false to opt out.
type WakeNudger ¶ added in v1.9.44
type WakeNudger struct {
// contains filtered or unexported fields
}
WakeNudger debounces per-parent wake nudges.
func NewWakeNudger ¶ added in v1.9.44
func NewWakeNudger(debounce time.Duration) *WakeNudger
NewWakeNudger returns a nudger that suppresses repeat nudges to the same parent within debounce of the previous one.
func (*WakeNudger) Nudge ¶ added in v1.9.44
func (w *WakeNudger) Nudge(parentID string, now time.Time, isIdle func() bool, send func() error) (bool, error)
Nudge fires one wake into parentID's pane via send IFF isIdle reports the pane idle AND the debounce window since the last nudge has elapsed. Returns whether a send was attempted, plus send's error (best-effort — callers may ignore it; a failed wake is harmless). now is injected for deterministic tests; production passes time.Now().
type WatcherAlertsSettings ¶ added in v1.6.0
type WatcherAlertsSettings struct {
// Enabled turns the bridge on. Default: false (no alerts emitted).
Enabled bool `toml:"enabled,omitempty"`
// Channels lists notification channel names the bridge's notifier should fan out to
// (e.g. "telegram", "slack", "discord"). Semantics are owned by the Notifier
// implementation; the bridge only passes the list to the notifier.
Channels []string `toml:"channels,omitempty"`
// DebounceMinutes is the per-(watcher x trigger) debounce window. Default: 15.
DebounceMinutes int `toml:"debounce_minutes,omitzero"`
}
WatcherAlertsSettings configures the health alerts bridge (REQ-WF-3). Opt-in via [watcher.alerts] in config.toml.
func (WatcherAlertsSettings) GetDebounceMinutes ¶ added in v1.6.0
func (a WatcherAlertsSettings) GetDebounceMinutes() int
GetDebounceMinutes returns the debounce window in minutes (default: 15).
type WatcherMeta ¶ added in v1.5.1
type WatcherMeta struct {
Name string `json:"name"`
Type string `json:"type"` // adapter type: "webhook", "ntfy", "github", "slack", "gmail"
CreatedAt string `json:"created_at"` // RFC3339 timestamp
WatchExpiry string `json:"watch_expiry,omitempty"` // RFC3339 UTC (gmail only) — Gmail watch() expiration
WatchHistoryID string `json:"watch_history_id,omitempty"` // uint64 as string (gmail only) — last processed Gmail history ID
}
WatcherMeta holds metadata for a named watcher instance. Persisted as meta.json in the effective watcher/<name> data directory.
func LoadWatcherMeta ¶ added in v1.5.1
func LoadWatcherMeta(name string) (*WatcherMeta, error)
LoadWatcherMeta reads meta.json for a named watcher.
type WatcherSettings ¶ added in v1.5.1
type WatcherSettings struct {
// MaxEventsPerWatcher is the maximum number of events to retain per watcher (default: 500)
MaxEventsPerWatcher int `toml:"max_events_per_watcher,omitzero"`
// MaxSilenceMinutes triggers a health warning when no events received (default: 60)
MaxSilenceMinutes int `toml:"max_silence_minutes,omitzero"`
// HealthCheckIntervalSeconds is the interval between health checks in seconds (default: 30)
HealthCheckIntervalSeconds int `toml:"health_check_interval_seconds,omitzero"`
// Alerts configures the health alerts bridge (opt-in). See WatcherAlertsSettings.
Alerts WatcherAlertsSettings `toml:"alerts,omitempty"`
}
WatcherSettings configures the event watcher system.
func (WatcherSettings) GetHealthCheckIntervalSeconds ¶ added in v1.5.1
func (w WatcherSettings) GetHealthCheckIntervalSeconds() int
GetHealthCheckIntervalSeconds returns the health check interval in seconds (default: 30).
func (WatcherSettings) GetMaxEventsPerWatcher ¶ added in v1.5.1
func (w WatcherSettings) GetMaxEventsPerWatcher() int
GetMaxEventsPerWatcher returns the max events per watcher (default: 500).
func (WatcherSettings) GetMaxSilenceMinutes ¶ added in v1.5.1
func (w WatcherSettings) GetMaxSilenceMinutes() int
GetMaxSilenceMinutes returns the silence threshold in minutes (default: 60).
type WebSettings ¶ added in v1.7.75
type WebSettings struct {
// MutationsEnabled controls whether POST/PATCH/DELETE endpoints accept
// requests. nil (omitted) defaults to true. Forced off by --read-only.
MutationsEnabled *bool `toml:"mutations_enabled,omitempty"`
}
WebSettings configures the `agent-deck web` HTTP server.
type WorktreeSettings ¶ added in v0.8.22
type WorktreeSettings struct {
// AutoCleanup: remove worktree when session is deleted (default: true, nil = true)
AutoCleanup *bool `toml:"auto_cleanup,omitempty"`
// DefaultEnabled controls whether worktree creation is pre-selected in
// new-session and fork dialogs by default.
// Default: false
DefaultEnabled bool `toml:"default_enabled,omitempty"`
// DefaultLocation: "sibling" (next to repo), "subdirectory" (inside .worktrees/),
// or a custom path (e.g., "~/worktrees") creating <path>/<repo_name>/<branch>
DefaultLocation string `toml:"default_location,omitempty"`
// PathTemplate: custom path template for worktree location.
// Variables:
// {repo-name}, {repo-root}, {session-id}
// {branch} -> sanitized (human-friendly, may collide)
// {branch-escaped} -> URL-escaped (collision-resistant, reversible)
// Unknown variables like {foo} are left as-is in the path.
// If set, overrides DefaultLocation.
PathTemplate *string `toml:"path_template,omitempty"`
// BranchPrefix is the prefix for auto-generated branch names when creating
// worktree sessions. For example, "feature/" produces "feature/my-session".
// Set to "" to disable auto-prefixing (just the session name).
// Default: "feature/" when not set.
BranchPrefix *string `toml:"branch_prefix,omitempty"`
// SetupTimeoutSeconds caps how long .agent-deck/worktree-setup.sh may run.
// Pointer (not plain int) so the loader can distinguish three cases:
// nil → field unset → 60s default (backward compat, GH #724)
// *0 → explicit unlimited (no deadline) — #727 follow-up
// *N (N > 0) → N seconds
// *N (N < 0) → treated as unset (60s default)
// The `*0 = unlimited` convention matches standard CLI tooling (curl,
// systemd, docker). Reporter @Clindbergh flagged the v1.7.65 behaviour
// (`0 = default`) as counter-convention in the PR review for #727.
SetupTimeoutSeconds *int `toml:"setup_timeout_seconds,omitempty"`
}
WorktreeSettings contains git worktree preferences.
func GetWorktreeSettings ¶ added in v0.8.22
func GetWorktreeSettings() WorktreeSettings
GetWorktreeSettings returns worktree settings with defaults applied
func (*WorktreeSettings) ApplyBranchPrefix ¶ added in v1.7.2
func (w *WorktreeSettings) ApplyBranchPrefix(branch string) string
ApplyBranchPrefix prepends the configured prefix to a branch name. If the branch name already starts with the expanded prefix, it is returned unchanged.
func (WorktreeSettings) GetAutoCleanup ¶ added in v1.9.61
func (w WorktreeSettings) GetAutoCleanup() bool
GetAutoCleanup returns whether worktree auto-cleanup is enabled (default: true).
func (*WorktreeSettings) Prefix ¶ added in v0.21.0
func (w *WorktreeSettings) Prefix() string
Prefix returns the branch prefix if set, or "feature/" if nil. Environment variables (e.g., $USER) in the prefix are expanded.
func (WorktreeSettings) SetupTimeout ¶ added in v1.7.65
func (w WorktreeSettings) SetupTimeout() time.Duration
SetupTimeout returns the configured worktree-setup-script timeout. Semantics (post-#727 follow-up):
- field unset (nil) or negative → DefaultWorktreeSetupTimeout (60s)
- explicit 0 → UnlimitedWorktreeSetupTimeout (no deadline)
- positive N → N seconds
func (*WorktreeSettings) Template ¶ added in v0.10.10
func (w *WorktreeSettings) Template() string
Template returns the path template if set, or empty string if nil.
Source Files
¶
- analytics.go
- archive.go
- builtins.go
- childenv.go
- claude.go
- claude_hooks.go
- claude_project_dir.go
- claude_resume_picker.go
- claude_title_reconcile.go
- claude_trust.go
- cli_status_refresh.go
- codex_mcp.go
- codex_subagent_gate.go
- codex_trust.go
- completion_ledger.go
- conductor.go
- conductor_bridge_embed.go
- conductor_claude_settings.go
- conductor_lock.go
- conductor_migrate_dir.go
- conductor_templates.go
- config.go
- copilot_hooks.go
- crush.go
- cursor_hooks.go
- cursor_mcp.go
- cursor_trust.go
- data_paths.go
- declarative_groups.go
- discovery.go
- done_sentinel.go
- env.go
- event_fingerprint.go
- event_watcher.go
- event_writer.go
- flicker_detector.go
- focus.go
- gemini.go
- gemini_analytics.go
- gemini_hooks.go
- gemini_mcp.go
- global_search.go
- group_claude_resolution.go
- group_concurrency.go
- group_view_mode.go
- groups.go
- hermes.go
- hermes_hooks.go
- hook_session_anchor.go
- hook_watcher.go
- idle_timeout_persist.go
- idle_timeout_watcher.go
- inbox.go
- inbox_consumer.go
- inbox_nudge.go
- inbox_nudge_wiring.go
- inbox_outbox.go
- inbox_stophook.go
- instance.go
- instance_mcp.go
- instance_spawn_guard.go
- instance_test_helper.go
- loadout.go
- maestro.go
- maintenance.go
- mcp_catalog.go
- mcp_child_reap.go
- migrate.go
- migrate_locate.go
- migration.go
- model_info.go
- multi_repo_worktree.go
- mutators.go
- namegen.go
- notifications.go
- opencode_mcp.go
- opencode_sse.go
- pathsuggest.go
- pin_refresh.go
- plugin_channels.go
- plugin_install.go
- pool_manager.go
- profile_migrate.go
- remote_keysender.go
- restart_guard.go
- restart_sweep.go
- reviver.go
- reviver_breaker.go
- rm_sweep.go
- selfheal_pass.go
- send_helper.go
- session_id_event_log.go
- skills_catalog.go
- spawn_failure.go
- ssh.go
- ssh_test_helper.go
- storage.go
- taskworker.go
- telegram_reliability.go
- telegram_validator.go
- tooloptions.go
- toolregistry.go
- transcript_done_scan.go
- transcript_streamer.go
- transition_daemon.go
- transition_notifier.go
- userconfig.go
- userconfig_merge.go
- utils.go
- watcher_meta.go
- worker_scratch.go
- worktree_removal.go
- zoxide.go