app

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: Apache-2.0 Imports: 52 Imported by: 0

Documentation

Overview

Package app is the top-level wiring of baifo: it owns the config, the providers, the MCPs registry, the storage layer, the builder, and the runner. The TUI talks to the rest of the system exclusively through the facade.Facade interface declared in this package, which lets the TUI stay testable in isolation and lets CLI sub-commands drive the same core without going through BubbleTea.

Index

Constants

This section is empty.

Variables

View Source
var ErrNoRoot = errors.New("no root agent configured")

ErrNoRoot is returned by SendMessage when the App was constructed without a root agent (e.g. because the config has no root.llm). The TUI uses this to render a friendly "configure a provider first" state instead of crashing.

Functions

This section is empty.

Types

type App

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

App is the concrete facade.Facade. It owns every long-lived resource and orchestrates the boot sequence described in ARCHITECTURE.md.

func New

func New(ctx context.Context, cfg *config.Config, configDir string) (*App, error)

New constructs an App from a loaded config and the active config dir. All collaborators are built lazily so a partially configured baifo (e.g. no encryption key) still boots — the missing pieces just degrade gracefully when accessed.

func (*App) AddFact

func (a *App) AddFact(ctx context.Context, content, category string) (uint64, error)

AddFact implements facade.Facade. Forwards to the store's AddManualEntry, marking the author as "user" so the agent can tell self-added facts apart from agent-saved ones when searching.

We do NOT emit a facade.ReloadEvent: the facts list reads on demand from SQLite, so the next overlay refresh picks the new row up by itself.

func (*App) AgentDetails

func (a *App) AgentDetails() []facade.AgentDetail

AgentDetails implements facade.Facade. Returns a one-line summary per agent. We include both the root and the sub-agents; the root is annotated in the Description prefix so the Settings overlay can distinguish it without needing a separate field on the facade DTO.

func (*App) AgentScaffold

func (a *App) AgentScaffold(suggestedName string) string

AgentScaffold implements facade.Facade by delegating to the package-level scaffold function. Kept as a method for facade.Facade conformance.

func (*App) AgentYAML

func (a *App) AgentYAML(name string) (string, error)

AgentYAML implements facade.Facade. Reads agents.yaml from disk and returns the exact YAML chunk of the named agent, comments and all. Falls back to a struct-reconstructed view if the entry exists in memory but not on disk (rare — only happens when the user just upserted via a different path and disk hasn't been re-read).

func (*App) Agents

func (a *App) Agents() []a2a.AgentEntry

Agents implements server.Core. Returns the live ADK agents this App wants served over A2A. Today this is just the root; later PRs append the static worker templates and any dynamic agents currently registered.

We return a2a.AgentEntry directly rather than a private DTO so the server package doesn't have to import internal/app for the type. This keeps the dependency direction app -> server (the composition root knows the server) instead of the inverse.

func (*App) AuthenticateMCP

func (a *App) AuthenticateMCP(ctx context.Context, name string, force bool) error

AuthenticateMCP implements facade.Facade. Delegates to the mcps registry, which knows how to run the right OAuth flow for this MCP's auth configuration.

func (*App) ClearMCPAuth

func (a *App) ClearMCPAuth(_ context.Context, name string) error

ClearMCPAuth implements facade.Facade. Delegates to the registry, which knows about both token and DCR-client stores.

func (*App) Close

func (a *App) Close() error

Close implements facade.Facade.

func (*App) CollectWorker

func (a *App) CollectWorker(ctx context.Context, id string) (string, error)

CollectWorker implements facade.Facade.

func (*App) ConfigDir

func (a *App) ConfigDir() string

ConfigDir implements facade.Facade.

func (*App) ConfiguredProviders

func (a *App) ConfiguredProviders() []modelstools.ProviderRef

ConfiguredProviders implements modelstools.Catalog. It returns the (name, type, url) of every provider declared in baifo.yaml so the list_models tool can map each one onto the embedded model catalogue (or flag a custom endpoint, when url is set).

func (*App) ContextGuardStatus

func (a *App) ContextGuardStatus(ctx context.Context) facade.ContextGuardStatus

ContextGuardStatus implements facade.Facade. It reflects the root agent's context_guard configuration against the live session state the contextguard plugin maintains, producing the gauge the TUI footer renders and the fingerprint it diffs to detect a fresh compaction.

The whole thing is best-effort: any missing piece (no guard block, no active session, unreadable state) collapses to a quiet zero value so the footer simply hides the chip rather than surfacing an error.

func (*App) DecodeSecrets

func (a *App) DecodeSecrets(ctx context.Context) (int, error)

DecodeSecrets unwraps every encrypted entry into the plaintext format and flips the on-disk flag. Requires baifo.encryption_key to be configured (we need the AEAD to decrypt). DESTRUCTIVE of confidentiality — callers MUST confirm with the user first. Returns the number of entries converted.

func (*App) DeleteAgent

func (a *App) DeleteAgent(ctx context.Context, name string) error

DeleteAgent implements facade.Facade. Removes the named entry from agents.yaml and triggers a reload.

Refuses to delete the root entry. The user is meant to either edit it in place (provider, prompt, ...) or, if they really want a different root, first flag a new agent and demote the old one — but until the root-swap UX is built, the simpler invariant ("you can't leave baifo without a root") is the safer one.

func (*App) DeleteFact

func (a *App) DeleteFact(ctx context.Context, entryID uint64) error

DeleteFact implements facade.Facade.

func (*App) DeleteMCPFromDisk

func (a *App) DeleteMCPFromDisk(ctx context.Context, name string) error

DeleteMCPFromDisk implements facade.Facade. It rewrites baifo.yaml without the named entry, preserving comments and unrelated keys via the yamledit package. The file watcher will fire ReloadFromDisk asynchronously; we also call it inline so the in-memory state is updated by the time DeleteMCPFromDisk returns — the watcher event then becomes a (cheap) no-op refresh.

func (*App) DeleteProvider

func (a *App) DeleteProvider(ctx context.Context, name string) error

DeleteProvider implements facade.Facade.

func (*App) DeleteSecret

func (a *App) DeleteSecret(ctx context.Context, name string) error

DeleteSecret implements facade.Facade. Removes the named secret from the store. Idempotent: missing names are not an error because the user's mental model is "make sure it's gone", and the Delete prompt already confirmed.

func (*App) DeleteSession

func (a *App) DeleteSession(ctx context.Context, id string) (string, error)

DeleteSession implements facade.Facade. When the deleted session was the active one, a fresh session is created and its ID is returned.

func (*App) DeleteSkill

func (a *App) DeleteSkill(ctx context.Context, name string) error

DeleteSkill implements facade.Facade. Defers the path sanity-check to the loader, which refuses anything that would escape the skills directory.

func (*App) EncodeSecrets

func (a *App) EncodeSecrets(ctx context.Context) (int, error)

EncodeSecrets re-seals every plaintext entry into the encrypted format. Requires baifo.encryption_key to be configured. Returns the number of entries converted (zero is fine, the call is idempotent).

func (*App) ExpandSecretString

func (a *App) ExpandSecretString(s string) (string, error)

ExpandSecretString resolves every ${secret:NAME} placeholder in s against the secrets store and returns the expanded string. A literal string with no placeholder is returned unchanged. When the store is not configured the placeholder is left intact and no error is raised, matching the rest of baifo's "secrets are optional in dev" posture (callers that require a real value validate it themselves).

Used by the boot path to resolve config fields that may reference a secret — e.g. the A2A bearer token (a2a.credentials.token) — so the real value never has to live in baifo.yaml in plaintext.

func (*App) FactContent

func (a *App) FactContent(entryID uint64) (content, category string, err error)

FactContent returns the current content of the named entry plus its category, so the TUI can pre-fill the editor for /fact edit. Surfaces a fmt-wrapped error when the ID doesn't exist; the underlying error is sufficient for the caller to render a 'not found' toast.

func (*App) FactDetails

func (a *App) FactDetails() []facade.FactDetail

FactDetails implements facade.Facade. Walks the facts store for the current user and surfaces every entry with its full metadata so the Settings overlay can render rich rows (timestamp + author + truncated content).

func (*App) InstallSkill

func (a *App) InstallSkill(ctx context.Context, sourceURL string) (string, error)

InstallSkill implements facade.Facade. Delegates to the installer package, which downloads, validates and extracts the archive into the loader's skills directory atomically. A facade.ReloadEvent is emitted so any open overlay refreshes.

func (*App) KillWorker

func (a *App) KillWorker(id string, reason string) error

KillWorker implements facade.Facade.

func (*App) ListAgentTemplates

func (a *App) ListAgentTemplates() []string

ListAgentTemplates implements facade.Facade.

func (*App) ListFacts

func (a *App) ListFacts() []string

ListFacts implements facade.Facade.

func (*App) ListMCPs

func (a *App) ListMCPs() []string

ListMCPs implements facade.Facade.

func (*App) ListProviders

func (a *App) ListProviders() []string

ListProviders implements facade.Facade.

func (*App) ListSecretNames

func (a *App) ListSecretNames() []string

ListSecretNames implements facade.Facade.

func (*App) ListSessions

func (a *App) ListSessions(ctx context.Context) ([]facade.SessionInfo, error)

ListSessions implements facade.Facade.

func (*App) ListSkills

func (a *App) ListSkills() []string

ListSkills implements facade.Facade.

func (*App) ListWorkers

func (a *App) ListWorkers() []facade.WorkerInfo

ListWorkers implements facade.Facade.

func (*App) MCPDetails

func (a *App) MCPDetails() []facade.MCPDetail

MCPDetails implements facade.Facade.

func (*App) MCPExternal

func (a *App) MCPExternal(name string) bool

MCPExternal implements spawn.Universe. Reports whether the named MCP uses an external transport (http / stdio), whose tools are resolved lazily and therefore can't be enumerated by MCPTools at boot.

func (*App) MCPScaffold

func (a *App) MCPScaffold(suggestedName string) string

MCPScaffold implements facade.Facade by delegating to the package-level scaffold function. It's a method on App for facade.Facade conformance.

func (*App) MCPTools

func (a *App) MCPTools(name string) []string

MCPTools implements spawn.Universe. Resolves the named MCP through the registry and returns the tool names it exposes. The list is embedded in the spawn_dynamic_agent description so the LLM can see, for instance, that the filesystem MCP ships with `exec`, `process_status` and `process_kill` in addition to read/write — without this enumeration the model hallucinates capabilities from the MCP name. Returns nil for unknown MCPs and for transports that have not been wired yet (HTTP/stdio).

func (*App) MCPYAML

func (a *App) MCPYAML(name string) (string, error)

MCPYAML implements facade.Facade. Reads baifo.yaml from disk and returns the exact YAML chunk of the named MCP entry, comments and all. Falls back to a struct-reconstructed view (no comments) when the entry exists in memory but not on disk — e.g. for a brand-new entry that was just upserted via a different path.

func (*App) MemoryService

func (a *App) MemoryService() memory.Service

MemoryService returns the long-term memory service backing the facts store. ADK's adka2a executor accepts a memory.Service for search_memory / save_to_memory tools; we hand over the same one the root agent already uses so a conversation that goes through the A2A boundary sees the same facts as the in-process path.

Returns nil when no facts store is wired (degraded boot); the executor handles a nil service gracefully.

func (*App) ModelName

func (a *App) ModelName() string

ModelName implements facade.Facade.

func (*App) NewSession

func (a *App) NewSession(ctx context.Context) (string, error)

NewSession implements facade.Facade.

func (*App) ProviderDetails

func (a *App) ProviderDetails() []facade.ProviderDetail

ProviderDetails implements facade.Facade. Returns a public, lightweight view per provider for /provider list and the Settings overlay. The API key is NEVER returned \u2014 only its presence is signalled.

func (*App) ProviderScaffold

func (a *App) ProviderScaffold(suggestedName string) string

ProviderScaffold implements facade.Facade by delegating to the package- level scaffold function. Method on App for interface conformance.

func (*App) ProviderYAML

func (a *App) ProviderYAML(name string) (string, error)

ProviderYAML implements facade.Facade. Reads baifo.yaml from disk and returns the exact YAML chunk for the named provider, comments preserved. Falls back to a struct-reconstructed view when the entry exists in memory but not on disk (rare, only after a pre-disk in-memory upsert).

func (*App) ReloadFromDisk

func (a *App) ReloadFromDisk(ctx context.Context) error

ReloadFromDisk implements facade.Facade. It re-reads baifo.yaml + agents.yaml and rebuilds providers, mcps, agent templates and the root agent. The active sessions, the workers manager and the storage layer are preserved — a config change is not a reason to evict live state.

Failure semantics: if any step fails the previous state remains untouched. The caller learns about the failure through the returned error but the App keeps serving requests with the old config.

func (*App) RenameSession

func (a *App) RenameSession(ctx context.Context, id, title string) error

RenameSession implements facade.Facade.

func (*App) RootAgent

func (a *App) RootAgent() agent.Agent

RootAgent returns the underlying ADK agent of the root instance. Exposed for headless callers that drive runner.Run directly and don't want to go through SendMessage / A2A. Returns nil while the root failed to build.

func (*App) RootBuildError

func (a *App) RootBuildError() error

RootBuildError implements facade.Facade.

func (*App) RootName

func (a *App) RootName() string

RootName implements facade.Facade.

func (*App) SecretDetails

func (a *App) SecretDetails() []spawntools.NamedDescription

SecretDetails implements spawn.Universe. It enriches the spawn_dynamic_agent description with the human-readable purpose of each secret (stored in secrets.yaml as `description:` per entry) so the LLM picks the right secret without trial and error. Returns nil when the secrets store is not initialised; values themselves are never included.

func (*App) SecretsEncrypted

func (a *App) SecretsEncrypted() bool

SecretsEncrypted reports whether the secrets store is currently running in encrypted mode. The TUI surfaces this as a badge in the Settings overlay so the user knows at a glance whether their secrets are at-rest encrypted.

func (*App) SendMessage

func (a *App) SendMessage(ctx context.Context, text string) iter.Seq2[*facade.Event, error]

SendMessage implements facade.Facade by delegating to the A2A request handler the App already builds for the `baifo server` endpoint. This gives the TUI and any remote A2A client identical semantics: same executor, same Task / Message / Artifact event stream, same translation layer. No HTTP — the call is a direct method invocation against the in-process handler.

The previous implementation called runner.Run directly and tried to dedup partial / final events with hand-rolled rules; those rules dropped the final answer of multi-round turns, surfacing as broken replies. adka2a.Executor already owns the right semantics, so we let it drive.

func (*App) SendToWorker

func (a *App) SendToWorker(ctx context.Context, id string, message string) error

SendToWorker implements facade.Facade.

func (*App) SessionEvents

func (a *App) SessionEvents(ctx context.Context, id string) ([]facade.Event, error)

SessionEvents implements facade.Facade.

Reads the full event log of a session from SQLite and translates each ADK session.Event into a facade.Event the TUI can render. The session store already drops streaming partials on persist, so the returned slice contains only committed turns.

Each Event carries Role ("user" / "model" / "") so the renderer can differentiate user bubbles from agent bubbles when repainting a resumed conversation. Tool calls and tool results are returned as separate Events (the TUI's existing chat renderer already pairs them by CallID for the bordered card).

Events that yielded no renderable payload after the Part walk (e.g. a system-only state delta) are skipped so the chat doesn't fill with empty rows.

func (*App) SessionID

func (a *App) SessionID() string

SessionID implements facade.Facade.

func (*App) SessionService

func (a *App) SessionService() session.Service

SessionService exposes the App's session service so the A2A executors share the same persistence layer as the in-process Facade. Pointer return is fine: the service is safe for concurrent use and outlives every per-agent executor.

func (*App) SetRootAgent

func (a *App) SetRootAgent(ctx context.Context, name string) error

SetRootAgent implements facade.Facade. Promotes the named agent to be the root: it sets root: true on that entry and strips the flag from whatever entry held it before, writing through yamledit so the rest of agents.yaml keeps its comments and order. A reload follows, which rebuilds the live root agent from the new on-disk definition.

This is the persistent counterpart to the TUI's `/root` (which only switches the chat view). Guards:

  • The target must exist in agents.yaml.
  • Promoting the current root is a friendly no-op (no write, no reload) so repeated invocations don't churn the file.

func (*App) SetSecret

func (a *App) SetSecret(ctx context.Context, name, value, description string) error

SetSecret implements facade.Facade. Writes the (name, value) pair to the secrets store under secrets.yaml. description is the human-readable note that the secrets list surfaces — never the value itself. Both name and value are required.

The store runs encrypted when baifo.encryption_key is set, and in plaintext mode otherwise (intended for local development).

We do NOT trigger ReloadFromDisk here: secrets are read on demand by the secrets store, not snapshotted into the running App state. A facade.ReloadEvent is emitted so any open Settings overlay refreshes its "(none / N entries)" badge.

func (*App) SkillContent

func (a *App) SkillContent(name string) (string, error)

SkillContent implements facade.Facade. Reads <skillsDir>/<name>/SKILL.md straight from disk to preserve comments and ordering. Returns a fmt-wrapped error when the file is missing so the TUI can surface it without exposing the absolute path.

func (*App) SkillDetails

func (a *App) SkillDetails() []facade.SkillDetail

SkillDetails implements facade.Facade. It walks the skills loader, parses each SKILL.md frontmatter once, and returns a compact summary. Errors on individual skills are swallowed so a single malformed SKILL.md does not blank the whole list — those skills just don't appear, and /skill edit can still load them via SkillContent.

func (*App) SkillScaffold

func (a *App) SkillScaffold(suggestedName string) string

SkillScaffold implements facade.Facade by delegating to the package-level scaffold function. It's a method on App for facade.Facade conformance.

func (*App) SpawnSkillDetails

func (a *App) SpawnSkillDetails() []spawntools.NamedDescription

SpawnSkillDetails is spawn.Universe.SkillDetails. The richer facade.Facade.SkillDetails returns a different (app-local) struct used by the TUI; this one shapes the same data into spawn's vocabulary so the spawn_dynamic_agent description carries skill names AND their frontmatter description — without that, the LLM has no idea what each skill actually does.

func (*App) SubscribeReload

func (a *App) SubscribeReload() <-chan facade.ReloadEvent

SubscribeReload implements facade.Facade.

func (*App) SubscribeWorker

func (a *App) SubscribeWorker(id string) ([]facade.WorkerStreamEvent, <-chan facade.WorkerStreamEvent, func(), error)

SubscribeWorker implements facade.Facade.

func (*App) SubscribeWorkerLifecycle

func (a *App) SubscribeWorkerLifecycle() (<-chan facade.WorkerLifecycleEvent, func())

SubscribeWorkerLifecycle implements facade.Facade. Subscribes the caller to the workers manager's global event bus, filters the firehose down to "interesting" lifecycle transitions (spawn + done/failed/killed) and translates each into the public facade.WorkerLifecycleEvent shape.

The returned channel is buffered at 32 entries. Lifecycle events are sparse — at most a handful per minute under realistic workloads — so the buffer is generous enough that even a TUI stuck on a slow render won't lose entries.

Implementation note: we track which workers we've already seen in a small map keyed by id. The first status-change event for a given id is treated as a "spawned" notice (it's the cheapest way to surface births without piggy-backing on the manager's internal goroutine; the manager publishes status-change unconditionally on every transition including the initial running→running one).

func (*App) SwitchSession

func (a *App) SwitchSession(ctx context.Context, id string) error

SwitchSession implements facade.Facade.

func (*App) TestMCPConnection

func (a *App) TestMCPConnection(ctx context.Context, name string) (string, error)

TestMCPConnection implements facade.Facade. Returns a single human-readable line describing the outcome — caller surfaces it in the chat row verbatim. Built-in MCPs report "in-process" since there's nothing remote to test; HTTP / stdio go through a full connect + ListTools round trip.

func (*App) UpdateFact

func (a *App) UpdateFact(ctx context.Context, entryID uint64, content string) error

UpdateFact implements facade.Facade. Bumps the entry's timestamp to now so the corrected fact appears at the top of subsequent searches — users edit when the agent recorded something slightly wrong, so the new content is the one that should win in future recall.

func (*App) UpsertAgent

func (a *App) UpsertAgent(ctx context.Context, yamlText string) error

UpsertAgent implements facade.Facade. Parses the buffer as an AgentTemplate, validates it against the existing schema rules in config.validateAgents (via a thin wrapper here), and writes through yamledit so the rest of agents.yaml keeps its comments and order.

On success we trigger a reload so the worker manager picks up the new template definition (workers spawned BEFORE the change are not re-templated; that's a deliberate keep-it-simple choice).

Root-flag invariants enforced here:

  • You cannot delete the root by dropping its flag: if the on-disk entry was the root, the incoming buffer must keep root: true.
  • You cannot add a second root by flagging a different name: at most one entry may carry root: true at any time.

func (*App) UpsertMCPFromDisk

func (a *App) UpsertMCPFromDisk(ctx context.Context, yamlText string) error

UpsertMCPFromDisk implements facade.Facade. Parses yamlText as one MCPEntry, validates the schema (per-type invariants enforced by mcps package), and writes it through yamledit to preserve comments in the rest of baifo.yaml. Triggers a reload so the in-memory registry stays in sync.

The buffer is parsed TWICE: once into a config.MCPEntry struct so we can validate it via mcps.NewRegistry, and once into a *yaml.Node so the comments the user wrote in the editor land in baifo.yaml verbatim. The struct path is throw-away; only the Node is persisted.

func (*App) UpsertProvider

func (a *App) UpsertProvider(ctx context.Context, yamlText string) error

UpsertProvider implements facade.Facade. Parses the buffer as a ProviderEntry, validates it through providers.NewRegistry, then writes through yamledit so comments and unrelated keys in baifo.yaml survive intact.

Validation goes through the same registry constructor the boot path uses, so a provider that passes here is guaranteed to pass on next start \u2014 no surprise at reload time.

func (*App) UpsertSkill

func (a *App) UpsertSkill(ctx context.Context, content string) error

UpsertSkill implements facade.Facade. Parses content via ADK's strict frontmatter validator, uses the parsed name as the directory slug, and writes the file. If the parsed name already exists, the file is overwritten in place (i.e. "edit" and "add" share the same path; the editor enforces "new vs existing" at the UX layer).

Note: baifo does NOT rename a skill's directory if the user changes the name field in the frontmatter. That would silently move the skill out from under any agent template that references the old slug. Renames are a future explicit verb.

func (*App) UserID

func (a *App) UserID() string

UserID returns the fixed user id this App uses for every session.

Jump to

Keyboard shortcuts

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