Skip to main content

App

Struct App 

Source
pub struct App {
Show 78 fields pub db: Db, pub space: Space, pub backends: Backends, pub saved: SavedCreds, pub active_space: Space, pub memory_model: String, pub transcriber_model: String, pub ocr_model: String, pub ocr_engine: String, pub local_ocr_model: String, pub embedding_model: String, pub image_gen_model: String, pub video_gen_model: String, pub searxng_url: String, pub langsearch_key: String, pub search_provider: String, pub base_system_prompt: String, pub verbosity: String, pub memory_rx: Option<UnboundedReceiver<(String, Vec<MemoryOp>)>>, pub compact_rx: Option<UnboundedReceiver<(String, String, i64, u64)>>, pub compacting_session_id: Option<String>, pub skills: Vec<Skill>, pub forced_skill: Option<String>, pub web_mode: bool, pub incognito: bool, pub incognito_img_dir: Option<PathBuf>, pub survey_gate: Option<SurveyGate>, pub survey_reply_tx: Option<UnboundedSender<String>>, pub research_steer_log: Vec<(usize, String)>, pub research_steer_acked: HashSet<usize>, pub research_stage_rows: Vec<String>, pub research_incognito: bool, pub research_steer_tx: Option<UnboundedSender<String>>, pub research_topic_rx: Option<UnboundedReceiver<Result<String, String>>>, pub research_live_input: String, pub toolbox: Arc<dyn ToolExecutor>, pub app_server: Option<AppServer>, pub skills_rx: Option<UnboundedReceiver<Result<String, String>>>, pub ocr_rx: Option<UnboundedReceiver<(String, String, OcrUpdate)>>, pub embed_rx: Option<UnboundedReceiver<EmbedMsg>>, pub ocr_pull_rx: Option<UnboundedReceiver<Result<String, String>>>, pub research_rx: Option<UnboundedReceiver<ResearchMsg>>, pub research_abort: Option<AbortHandle>, pub login_rx: Option<UnboundedReceiver<LoginMsg>>, pub update_rx: Option<UnboundedReceiver<Option<String>>>, pub swarm_rx: Option<UnboundedReceiver<(String, SwarmUpdate)>>, pub swarm_abort: Option<AbortHandle>, pub swarm_session: Option<String>, pub swarm_cache: Vec<Persona>, pub research_running: Option<(String, String)>, pub files_cache: Vec<FileRow>, pub apps_cache: Vec<String>, pub images_cache: Vec<ImageMeta>, pub scripts_cache: Vec<ScriptMeta>, pub watches_cache: Vec<Watch>, pub usage_range: UsageRange, pub models: Vec<Model>, pub current_model: Option<String>, pub models_rx: Option<UnboundedReceiver<Result<Vec<Model>, String>>>, pub favorites: HashSet<String>, pub last_used: HashMap<String, String>, pub reasoning: HashMap<String, String>, pub session: Option<Session>, pub messages: Vec<Message>, pub chat_event_tx: UnboundedSender<ChatEvent>, pub chat_event_rx: UnboundedReceiver<ChatEvent>, pub chat_tasks: HashMap<ChatTaskId, ChatTask>, pub next_chat_task_id: ChatTaskId, pub notifications: VecDeque<ChatNotification>, pub unread: HashSet<String>, pub context_total: Option<u64>, pub last_cache_rate: Option<f64>, pub settings: Settings, pub model_pick_target: ModelPickTarget, pub spinner_frame: usize, pub thinking_idx: usize, pub spinner_color: SpinnerColor, pub sessions_cache: Vec<Session>, /* private fields */
}

Fields§

§db: Db§space: Space§backends: Backends

Every backend currently logged into. /model merges all of their catalogs into one list; picking a model resolves back to the right one here.

§saved: SavedCreds

Every credential configured on disk, kept in sync with backends.

§active_space: Space

The space the current/next session belongs to.

§memory_model: String

Model used for background memory extraction (empty = disabled).

§transcriber_model: String

Model used for image transcription (empty = disabled).

§ocr_model: String

Vision model for scanned-PDF OCR (empty = tesseract only).

§ocr_engine: String

OCR engine choice: “auto” (vlm when ocr_model set), “tesseract”, “vlm”, or “local” (Ollama on 127.0.0.1:11434, set up by cycling to it in /config).

§local_ocr_model: String

Ollama model name for the “local” OCR engine.

§embedding_model: String

Embedding model for semantic file search (empty = keyword FTS only).

§image_gen_model: String

Model used for AI image generation (empty = disabled).

§video_gen_model: String

Model used for AI video generation (empty = disabled).

§searxng_url: String

Base URL of a SearXNG instance for the web-search tool, or empty to disable it. Configured in-app (Ctrl+O settings), not a config file.

§langsearch_key: String

LangSearch API key (free tier), or empty to disable it.

§search_provider: String

Which web-search backend to prefer: “auto”/“langsearch”/“searxng”/“duckduckgo”.

§base_system_prompt: String

Raw contents of system_prompt.md (with an unresolved {{verbosity}} placeholder) — the app’s own base system prompt, $EDITOR-editable.

§verbosity: String

Answer-length preference woven into the system prompt: “normal”, “concise” (default), or “caveman”.

§memory_rx: Option<UnboundedReceiver<(String, Vec<MemoryOp>)>>§compact_rx: Option<UnboundedReceiver<(String, String, i64, u64)>>

Background compaction result: (session id, digest, messages-covered, pre-compaction %).

§compacting_session_id: Option<String>

Session currently being compacted. Kept separately from compact_rx so the TUI can mark the right row while the job is still in flight.

§skills: Vec<Skill>

Discovered skills (name/description only — bodies are read from disk on invocation, so this list is cheap and reloaded whenever it changes).

§forced_skill: Option<String>

A skill armed by /<skill-name>, injected into the next message only.

§web_mode: bool

/web answer mode for the active session (or the next one created).

§incognito: bool§incognito_img_dir: Option<PathBuf>

Temp directory for incognito image files, cleaned up on toggle.

§survey_gate: Option<SurveyGate>

A parked conversation’s chat-reply gate (clarifying questions or an approval) — armed only while a reply is actually pending, so a gate in another session can never swallow typing.

§survey_reply_tx: Option<UnboundedSender<String>>

Sender half of the reply channel into a parked gate. Created at the owning job’s start; the gate itself arms/disarms as pending-section updates arrive.

§research_steer_log: Vec<(usize, String)>

Every /steer queued during the current job, as (queue position, text) — position 1-based, assigned in queue order. Entries are dropped once the pipeline acknowledges them (research_steer_acked), and the whole log is cleared when the job stops or its channel closes, so retained steer text stays bounded per job.

§research_steer_acked: HashSet<usize>

Steer positions (steer #N) the pipeline has drained and persisted — parsed from Stage updates in on_research_done, so the live popup knows what’s picked up even when opened from another session, and the retained log can drop acknowledged entries.

§research_stage_rows: Vec<String>

The running job’s stage rows (label: detail content strings), kept in sync by mirror_stage regardless of which session is viewed — the live popup renders from here instead of re-reading the db per frame.

§research_incognito: bool

Incognito mode captured when the job started: artifact persistence (plan files, and the plan message itself, which folds in survey replies) is decided by this, never by toggling incognito mid-job.

§research_steer_tx: Option<UnboundedSender<String>>

Queues /steer instructions into the currently running research job’s round-boundary check. None when no research job is running.

§research_topic_rx: Option<UnboundedReceiver<Result<String, String>>>

In-progress /research (no args) topic distillation from recent chat.

§research_live_input: String

Composer buffer for the live research-activity view’s steer input.

§toolbox: Arc<dyn ToolExecutor>§app_server: Option<AppServer>

Local static server for model-created apps (None if it failed to bind).

§skills_rx: Option<UnboundedReceiver<Result<String, String>>>§ocr_rx: Option<UnboundedReceiver<(String, String, OcrUpdate)>>

Background OCR updates: (space_id, file name, progress or final result).

§embed_rx: Option<UnboundedReceiver<EmbedMsg>>

One in-flight chunk-embedding job: (space id, file id, vectors or error).

§ocr_pull_rx: Option<UnboundedReceiver<Result<String, String>>>

A running local-OCR-model pull: model name on success, error text on failure.

§research_rx: Option<UnboundedReceiver<ResearchMsg>>

A running /research job’s channel and cancellation handle.

§research_abort: Option<AbortHandle>§login_rx: Option<UnboundedReceiver<LoginMsg>>§update_rx: Option<UnboundedReceiver<Option<String>>>

Startup update check result (newest published version, or None on failure).

§swarm_rx: Option<UnboundedReceiver<(String, SwarmUpdate)>>

A running /swarm discussion’s channel, cancellation handle, and origin session id (used for targeting the correct progress row).

§swarm_abort: Option<AbortHandle>§swarm_session: Option<String>§swarm_cache: Vec<Persona>

The active session’s /swarm roster, cached for the popup (kept in sync by on_swarm_update while a turn runs; the view owns the cursor).

§research_running: Option<(String, String)>

(session id, topic) of the /research job currently running, if any — cleared when its channel closes.

§files_cache: Vec<FileRow>

The active space’s imported files (refreshed by rescan_files).

§apps_cache: Vec<String>

The space’s apps (/apps popup): names, cursor, and mode.

§images_cache: Vec<ImageMeta>

The space’s images (/image popup): cache and cursor.

§scripts_cache: Vec<ScriptMeta>

The space’s scripts (/script popup): cache, cursor, and edit buffer.

§watches_cache: Vec<Watch>

The space’s standing research watches (/watch picker): cache + cursor.

§usage_range: UsageRange

Time window the /usage dashboard aggregates (24h/7d/30d/all) — a persisted preference, applied by apply_setting on load.

§models: Vec<Model>

Live model catalog (fetched on demand, never hardcoded).

§current_model: Option<String>§models_rx: Option<UnboundedReceiver<Result<Vec<Model>, String>>>§favorites: HashSet<String>

Model ids marked favorite, and when each model was last used (rfc3339).

§last_used: HashMap<String, String>§reasoning: HashMap<String, String>

Per-model reasoning effort (wire string from ReasoningEffort::as_str, e.g. “minimal” / “low” / “high” / “xhigh” / “max” / “none”).

§session: Option<Session>§messages: Vec<Message>§chat_event_tx: UnboundedSender<ChatEvent>

Central event channel for all in-flight chat tasks.

§chat_event_rx: UnboundedReceiver<ChatEvent>§chat_tasks: HashMap<ChatTaskId, ChatTask>§next_chat_task_id: ChatTaskId§notifications: VecDeque<ChatNotification>

Completed task notifications, kept independently of the one-line status.

§unread: HashSet<String>

Sessions holding a response that finished while the user was elsewhere.

§context_total: Option<u64>

Exact conversation token total from the last completed response.

§last_cache_rate: Option<f64>

Cache hit rate of the most recent completed request (0..=1), shown next to the context window. Transient — not persisted here; the per-request numbers live in usage_log.

§settings: Settings§model_pick_target: ModelPickTarget

What a confirmed model-picker selection is currently for (the active session’s model, a feature model from /config, or a swarm persona).

§spinner_frame: usize

Animated “thinking” indicator shown while a response streams.

§thinking_idx: usize§spinner_color: SpinnerColor§sessions_cache: Vec<Session>

Implementations§

Source§

impl App

Source

pub fn app_url(&self, name: &str) -> Option<String>

An app’s live URL, when the server is running.

Source

pub fn app_file_count(&self, name: &str) -> usize

How many files an app holds (recursive; node_modules counted as one “deps” marker would be noise, so it’s skipped entirely).

Source§

impl App

Source

pub fn send_message(&mut self, text: String) -> Result<()>

Send one chat message. AppView::submit is the composer front — it reads the TextArea and routes through run_command / Send — so this is the domain half: validation, session auto-creation, persistence.

Source

pub fn build_history(&mut self) -> Vec<ChatMessage>

The exact message list a completion request will carry: system prompt, compaction digest, forced skill, then the effective conversation tail. User messages with images become multimodal parts for vision models, or get their stored descriptions appended as text for everything else.

Source

pub fn start_stream(&mut self) -> Result<()>

Build history and fire one independently-routed streaming request.

Source

pub fn on_chat_event( &mut self, task_id: ChatTaskId, ev: StreamEvent, ) -> Result<()>

Source

pub fn cancel_chat_task(&mut self, task_id: ChatTaskId) -> Result<()>

Cancel one in-flight chat task by id (kills any in-flight request and tool loop) and keep whatever text already arrived.

Source

pub fn stop_stream(&mut self) -> Result<()>

Esc while a response streams: abort the background chat task (kills any in-flight request and tool loop) and keep whatever text already arrived.

Source

pub fn discard_chat_task(&mut self, session_id: &str)

Source

pub fn maybe_generate_title(&mut self)

After the first exchange of a session, ask the model for a short topic and slug in the background. Runs once per session (guarded by slug.is_none()).

Source

pub fn on_title_result(&mut self, result: Option<(String, String, String)>)

Apply a generated topic/slug to the matching session (in memory + db).

Source

pub fn system_prompt(&self) -> String

Instructions + the active session’s memory snapshot, combined into one system message. The full system prompt puts the app’s own base prompt (identity/formatting rules, $EDITOR-editable) first, then space instructions, skills, and the frozen memory snapshot layered on top. Unlike those three, the base prompt is never empty — it’s the app speaking, not per-space configuration.

o in the history pane: open the [n] citation under the current text selection (via the open crate), resolved against the Sources list of the message the selection belongs to. Every miss surfaces as a status message rather than doing nothing silently. owner is the message index at the selection start, computed by the view layer from its HistorySel state. Ctrl+O: navigate to the session linked in a session_link message under the text selection. Expects the message content’s first line to be the target session id.

Source

pub fn flag_source_under_selection( &mut self, flag: Option<&str>, selected: Option<String>, owner: Option<usize>, )

Pin or discard the [n] source under the current history selection (same selection→citation resolution as open_citation_under_selection). Flags are keyed by the message’s normalized URL, session-scoped. selected/owner come from the view’s HistorySel state.

Source

pub fn toggle_web_mode(&mut self)

/web: flip web answer mode for the active (or about-to-be-created) session. Persisted immediately if a session already exists; otherwise applied to the session created by the next message.

Source

pub fn toggle_incognito(&mut self) -> Result<()>

Source

pub fn resolved_base_system_prompt(&self) -> String

base_system_prompt (raw, as read from system_prompt.md) with the {{verbosity}} placeholder swapped for the level the user picked.

Source

pub fn reload_base_system_prompt(&mut self)

Re-read system_prompt.md after a Ctrl+E hand-edit.

Source

pub fn list_apps(&self) -> Vec<String>

Names of the active space’s existing apps (directory listing).

Source§

impl App

Source

pub fn parse_command(&self, cmd: &str) -> Result<AppCommand, String>

Parse a /-command line (without the leading slash) into the command seam. Resolves aliases via the COMMANDS catalog and recognizes /<skill-name> arms. Err carries a status-line message for unknown commands — the TUI shows it without failing the key handler.

Source

pub fn execute(&mut self, cmd: AppCommand) -> Result<()>

Run one parsed command — the mutation path for domain intents. The TUI’s AppView::execute intercepts the view-only commands (quit, popup opens, the watch picker) before delegating here; headless consumers only ever send domain commands. run_command is the /-string parse front.

Source

pub fn run_command(&mut self, cmd: &str) -> Result<()>

The /-string front: parse into the seam, then execute. Unknown commands surface as a status line rather than an error.

Source§

impl App

Source

pub fn excluded_from_model_history(m: &Message) -> bool

Rows that must never reach a model — neither in the raw history (build_history) nor in a compaction digest: background-job scratch (research stage/plan/survey rows), transport failures, session links, per-persona swarm round replies (the turn’s synthesis carries the context), gate replies — the survey/plan sections they answer are excluded too, so a bare “drop Q2” must not leak into later turns via a digest — and the compaction digest row itself (the digest is already fed to the model via compact_summary, so a transcript row must never be sent twice).

Source

pub fn effective_messages(&self) -> &[Message]

The messages actually sent on the next turn: everything after the session’s compaction boundary, or all of them if it hasn’t compacted (yet). The full, uncompacted history stays in self.messages/the db for scrollback — only what’s sent shrinks.

Source

pub fn is_compacting_session(&self, id: &str) -> bool

Whether id is the session whose background compaction is still running.

Source

pub fn is_compacting_current_session(&self) -> bool

Whether the session currently on screen is being compacted.

Source

pub fn maybe_compact(&mut self)

After a reply, auto-compact once context usage crosses the configured threshold (0 disables it).

Source

pub fn force_compact(&mut self)

Manually trigger compaction right now (/compact), ignoring the threshold. No-ops with a status message if there’s nothing to compact.

Source

pub fn on_compact_result(&mut self, result: Option<(String, String, i64, u64)>)

Apply a compaction digest to the matching session (in memory + db): the digest itself becomes a visible compaction transcript row at the compaction boundary, so what was folded away is shown in the chat instead of being reachable only through the context popup’s editor. A later compaction updates that row in place (one digest row per session, at the same boundary). Clears the exact usage total — it reflects the pre-compaction request, so context_used should fall back to the (now accurate) estimate until the next real response reports fresh usage.

Source

pub fn backfill_compaction_row(&mut self)

Sessions compacted before compaction rows existed (or loaded from a db written by such a version) carry the digest only in compact_summary. Surface it as a transcript row at the boundary, exactly like a fresh compaction would, so the digest is never hidden behind the context popup. Idempotent: no-ops once a compaction row exists. Called after every session load.

Source

pub fn context_breakdown(&self) -> ContextBreakdown

System/memory/conversation token estimate for the context breakdown popup (Ctrl+I). Each bucket is a ~4-chars/token estimate, same method context_used falls back to, so the parts add up to (roughly) the whole.

Source

pub fn compact_summary_path(&self) -> Option<PathBuf>

Path to a temp file holding the active session’s compaction digest, so it can be viewed/edited in $EDITOR from the context popup (Ctrl+G, v). None if the session hasn’t been compacted yet.

Source

pub fn reload_compact_summary(&mut self, path: &Path) -> Result<()>

Read path (from compact_summary_path) back after $EDITOR closes — hand-edits to the digest persist (db + in-memory), same as any other file-backed edit in the app.

Source§

impl App

Source

pub fn export_report(&mut self) -> Result<()>

/export: write the active session’s latest research report (the most recent assistant message) plus its citations to <space>/files/reports/<session-slug>.md, overwriting any earlier export of the same session. No-op with a status message if the session has no research report yet.

Source§

impl App

Source

pub fn rescan_files(&mut self)

Sync the active space’s files directory with the db: new or changed files (by sha256) are re-extracted and re-indexed, rows for deleted files are dropped, and files_cache is refreshed. Best-effort: a single bad file gets an “error: …” status instead of failing the scan. ponytail: runs synchronously on the UI task — extraction of a huge PDF blocks a beat; move to a blocking task if that ever hurts.

Source

pub fn start_ocr(&mut self, jobs: Vec<(String, String, PathBuf)>)

OCR queued scanned PDFs sequentially off the UI thread. One batch at a time: jobs arriving while a batch runs stay at “ocr…” and re-queue on a later rescan.

Source

pub fn ocr_backend(&self) -> Option<OcrBackend>

The vision backend scanned PDFs OCR through, or None for tesseract: “local” → Ollama; “vlm”/“auto” with an OCR model + provider → OpenRouter.

Source

pub fn ocr_local_install(&mut self, arg: &str)

Cycling the OCR engine to “local” (in /config) pulls a local OCR model through Ollama in the background and switches the engine to it when the pull succeeds. Defaults to glm-ocr (0.9B — the current open OCR benchmark leader).

Source

pub fn on_ocr_pull(&mut self, r: Option<Result<String, String>>)

The local-OCR-model pull finished: point the OCR engine at the local model.

Source

pub fn reextract_file(&mut self, name: &str)

The reextract/reocr/delete popup flows live in the view layer; this is the re-extract half: zero the selected file’s chunks and hash/size so the next rescan re-indexes from disk.

Source

pub fn reocr_file(&mut self, name: &str)

The reocr popup flow lives in the view layer; this is the OCR half: force an OCR pass on one file, bypassing text extraction entirely. Useful when pdf_extract gives unreliable text and you want VLM OCR output instead.

Source

pub fn start_embedding(&mut self)

Embed the next imported file whose chunks lack vectors, one file per job (the done-handler chains the next). Files with no extractable text receive a small filename metadata chunk so they are still searchable. No-op without a provider, without an embedding model, or while a job is already in flight.

Source

pub fn on_embed_done(&mut self, r: Option<EmbedMsg>)

One embedding job finished: store vectors and chain the next file, or surface the error and stop (a dead endpoint shouldn’t be hammered — the next rescan retries). Search falls back to keywords while vectors are missing.

Source

pub fn on_ocr_done(&mut self, r: Option<(String, String, OcrUpdate)>)

A finished OCR job: persist chunks/status, refresh the cache only if the file’s space is still active. None = batch done (channel closed).

Source

pub fn import_file(&mut self, path: &Path) -> Result<String>

Copy path into the active space’s files dir and index it. Returns the imported file’s name. An existing file with the same name is overwritten (the rescan re-extracts it).

Source

pub fn delete_file(&mut self, name: &str) -> Result<()>

Domain half of the files popup’s delete: remove the disk copy and index rows, refresh the cache. The view owns the mode/selection state.

Source

pub fn rename_file(&mut self, name: &str, new: &str) -> Result<()>

Domain half of the files popup’s rename: move the file on disk; the rescan swaps the index rows (old name dropped, new name re-extracted). Returns an error message string when the target already exists or the name is invalid; the view turns it into a status line.

Source§

impl App

Source

pub fn switch_space_cli(&mut self, name: &str) -> Result<()>

Switch the active space by name (--space). Bails on an unknown name; no-op when it’s already the active space.

Source

pub async fn run_turn( &mut self, prompt: String, opts: TurnOpts, ) -> Result<(String, Option<Usage>)>

Drive one non-interactive turn: send prompt, drain stream events until the response finishes, and (unless opts.stream) collect the answer. Tool status lines and the token summary go to stderr unless opts.quiet. Returns the final answer text and merged usage.

Source

pub async fn ask_headless( &mut self, prompt: String, opts: TurnOpts, ) -> Result<AskOutcome>

nexus ask: one turn, then a short wait for the model-generated session title so nexus sessions shows a real name instead of the prompt prefix. A slow title must never hold the ask hostage — capped.

Source

pub async fn chat_headless(&mut self, quiet: bool) -> Result<()>

nexus chat: a bare REPL — prompt on stderr, one turn per line, all turns in the one session the first turn creates.

Source

pub async fn research_headless( &mut self, topic: String, approve: bool, opts: TurnOpts, ) -> Result<ResearchOutcome>

nexus research <topic>: run the full deep-research pipeline headless.

Gate policy: with approve the pipeline runs ungated (survey and plan-approval are skipped entirely, like /research!). Without it, a gated run parks at each SurveyReady/PlanReady: when stdin is a terminal the prompt is printed and a reply is read from the line (empty reply skips the survey round; approve approves the plan); otherwise the run bails rather than hang.

Source

pub async fn watch_run_headless( &mut self, watch_ref: Option<&str>, all: bool, quiet: bool, ) -> Result<Vec<(String, ResearchOutcome)>>

nexus watch run: run one watch (by id or topic prefix), all watches (--all), or the due ones (default). Each run drives its research job to completion before the next starts. Returns the reports, keyed by watch topic, for the caller to print.

Source§

impl App

Source

pub fn refresh_images(&mut self)

Read the space’s images dir and populate images_cache (name, size, modified) with image files only — the Files tab owns everything else. A missing or empty dir produces an empty cache, never an error.

Source

pub fn delete_image_file(&mut self, name: &str) -> Result<bool>

The popup’s confirm-delete lives in the view; this is the disk half: remove the file (if any) and refresh the cache. Returns whether a row existed. images_mode reset is the view’s job.

Source§

impl App

Source

pub fn read_memory(&self) -> String

Raw contents of the active space’s memory file, capped to ~120k chars (~30k tokens — headroom is cheap on 1M-context models; this just stops a runaway file from eating the whole budget).

Source

pub fn refresh_memory_snapshot(&mut self)

Reload the active session’s memory snapshot and begin a new prompt-cache epoch. New inferred facts normally do not call this while a session is active; switching sessions or an explicit refresh does.

Source

pub fn memory_snapshot(&self) -> &str

Return the memory text that is stable for the active cache epoch.

Source

pub fn maybe_extract_memory(&mut self)

After an assistant reply, ask the memory model for ADD/UPDATE/DELETE ops against the space’s fact file. No-op if extraction is disabled or the last exchange is unavailable.

Source

pub fn on_memory_result(&mut self, result: Option<(String, Vec<MemoryOp>)>)

Apply extracted ops to the active space’s memory file, if it’s still the active one (a meanwhile space-switch discards stale results).

Source§

impl App

Source

pub fn rebuild_all_backends(&mut self)

Rebuild every backend from the on-disk saved credentials (after boot, or after a settings change). Keeps saved and backends in sync.

Source

pub fn resolve_model_backend(&self, id: &str) -> Option<(OpenRouter, String)>

Source

pub fn resolve_utility_model_backend( &self, configured_id: &str, ) -> Option<(OpenRouter, String)>

Resolve a feature (non-session) model that may be a bare wire id or a composite id. Feature models default to the session provider’s research-class default; a composite id picks its own backend.

Source

pub fn resolve_feature_model_backend( &self, configured_id: &str, default: fn(&OpenRouter) -> &'static str, ) -> Option<(OpenRouter, String)>

Resolve a feature model by name for a backend that may not be OpenRouter (used for image/video gen where the user may have picked a non-OpenRouter model).

Source

pub fn fetch_models(&mut self)

Fetch every configured backend’s catalog concurrently and merge them into one list. A backend that fails is dropped from the merge (its error is only surfaced if every backend failed) — one flaky login shouldn’t blank out the models of the others. Public so the view layer can re-trigger a fetch from the model picker.

Source

pub fn context_limit(&self) -> Option<u64>

Context window of the active model, if known.

Source

pub fn context_used(&self) -> u64

Tokens used by the current session. Exact (from the provider’s usage on the last response) when idle; a ~4-chars/token estimate while streaming or before the first response. Estimate is what would actually be sent — system/memory prompt, the compaction digest (if any), and only the tail after it, not the full (possibly much larger) on-screen scrollback.

Source

pub fn current_model_supports_images(&self) -> bool

Whether the active model accepts image input (unknown model → false).

Source

pub fn reasoning_of(&self, id: &str) -> Option<&str>

Source

pub fn effort_accepted(&self, model: &str, effort: &str) -> bool

Whether effort is in model’s accepted reasoning set, so a stored value is only sent when the model actually accepts it. Unknown models (not in the loaded catalog) accept anything — never silently drop a stored value just because the catalog isn’t fetched yet.

Source

pub fn pick_model(&mut self, id: &str) -> Result<()>

Set the active model (or a feature model, per the pick target). The view layer owns the popup routing; this is the domain half.

Source

pub fn popup_after_pick(target: ModelPickTarget) -> Popup

The popup a confirmed pick should return to, given the pick target — view-layer helper (the picker opens from /config for feature models).

Source

pub fn clear_memory_model(&mut self) -> Result<()>

Disable memory extraction entirely (Backspace on the memory-model row in /config).

Source

pub fn clear_transcriber_model(&mut self) -> Result<()>

Disable image transcription entirely (Backspace on the transcriber-model row in /config).

Source

pub fn clear_ocr_model(&mut self) -> Result<()>

Disable VLM OCR (Backspace on the OCR-model row in /config).

Source

pub fn clear_image_gen_model(&mut self) -> Result<()>

Disable image generation (Backspace on the image gen model row in /config).

Source

pub fn clear_video_gen_model(&mut self) -> Result<()>

Disable video generation (Backspace on the video gen model row in /config).

Source

pub fn start_codex_login(&mut self)

/login: start the OpenAI Codex device-code login (the only backend without a plain API key). Domain side: spawns the task and owns the result channel; the view layer shows the selector.

Source

pub fn on_login_result(&mut self, msg: Option<LoginMsg>)

Source§

impl App

Source

pub fn start_research(&mut self, topic: &str)

/research <topic>: run the multi-agent research pipeline in a new background session. One job at a time. /research! <topic> skips the plan-approval gate.

Source

pub fn steer_research(&mut self, text: &str)

/steer <text>: queue an extra instruction for the running research job, picked up at the next round boundary. No-op with a status message if no research job is running.

Source

pub fn start_research_from_chat(&mut self)

/research with no topic: distill one from the last ~20 chat turns (one cheap completion, same background-channel shape as maybe_generate_title) then hand it to start_research_with_gate exactly as if it had been typed — the existing plan-approval gate still lets you bail or edit before searchers run.

Source

pub fn on_research_topic_derived(&mut self, r: Option<Result<String, String>>)

The topic-distillation job finished: start research with it, or report the failure. None = channel closed without a result.

Source

pub fn start_research_with_gate(&mut self, topic: &str, gated: bool)

Source

pub fn stop_research(&mut self)

Abort the active research pipeline, including survey/searcher/tool streams spawned under its orchestration task.

Source

pub fn survey_gate_targets_current_session(&self) -> bool

Whether the survey gate (clarifying questions or plan approval) is armed for the currently viewed session — the only case where Enter is intercepted and routed to the pipeline instead of a normal chat send. A gate in another session must never swallow typing (the old cross-session hijack).

Source

pub fn restore_survey_gate_prompt(&mut self)

Restore an actionable gate row after loading its session. Normal jobs already load the persisted row, while incognito jobs recover it from SurveyGate without writing private content to the database.

Source

pub fn set_survey_gate(&mut self, gate: Option<SurveyGate>)

Route a chat reply into the parked survey gate (survey answer or plan approval/edit). Records the reply as a gate_reply in the session — it renders in the transcript like a user message but is never replayed to the model, since the survey/plan rows it answers are excluded too: a bare “the second option” or “drop Q2” must not leak into model history without its context. Arm or clear the parked gate, emitting a Gate event either way. The event carries the session id so consumers can compare it against the viewed session — a gate in another session must never swallow typing.

Source

pub fn reply_to_survey_gate(&mut self, text: &str)

Source

pub fn on_research_done(&mut self, r: Option<ResearchMsg>)

A research pipeline update: a stage label, or the final report/error. None = the job’s channel closed (fires once, right after Done).

Source§

impl App

Source

pub fn refresh_scripts(&mut self)

Read the space’s scripts dir and populate scripts_cache. A missing or empty dir produces an empty cache, never an error. The scripts popup’s flow (selection/edit state, $EDITOR handoff) lives in the view layer.

Source

pub fn ensure_script_file(&mut self, name: &str) -> Result<PathBuf>

Domain half of script create: touch the file (if absent) and refresh the cache. Returns the created path. The view owns the edit buffer and the $EDITOR handoff.

Source

pub fn rename_script_file(&mut self, from: &str, to: &str) -> Result<()>

Domain half of script rename: move the file on disk. Returns an error message string when the target already exists (the view turns it into a status line); Ok otherwise.

Source

pub fn delete_script_file(&mut self, name: &str) -> Result<bool>

Domain half of script delete: remove the file from disk and refresh. Returns whether a row existed.

Source§

impl App

Source

pub fn activate_notification(&mut self, index: usize) -> Result<()>

Source

pub fn new_session(&mut self)

Clear back to a blank conversation. Doesn’t touch the db — a session row is only created lazily on the first message actually sent (same as the very first message of the app), so /new without typing anything doesn’t leave an empty “new chat” behind in the session list.

Source

pub fn switch_to_session_by_id(&mut self, id: &str) -> Result<()>

Switch to a session by its id. Used by session-link navigation (Ctrl+O), notification clicks, the ResolveSession command, and the session picker’s confirm (via the view layer).

Source§

impl App

Source

pub fn cycle_ocr_engine(&mut self) -> Result<()>

Advance the OCR engine auto → tesseract → vlm → local → auto, persisted. Cycling into “local” pulls the configured model via ollama in the background (formerly the separate /ocr-local command) — ocr_local_install itself flips the engine to “local” and persists it once the pull actually succeeds, so a failed pull doesn’t leave the engine silently pointed at a model that was never fetched.

Source

pub fn set_setting(&mut self, key: &str, value: &str) -> Result<()>

Set one named setting by key, persisting it and applying it live — the SetSetting command the host (and later the TUI) uses. Unlike load_settings (which ignores unknown persisted rows), this fails fast: an unknown key or an invalid value for a constrained key is an error, never a silent no-op reported as success.

Source§

impl App

Source

pub fn reload_skills(&mut self)

Re-read discovered skills from disk (after an install/remove, an external Agent Skills change, or a Ctrl+E hand-edit of SKILL.md). The view re-clamps its cursor after calling this.

Source

pub fn skill_is_app_managed(&self, skill: &Skill) -> bool

Whether the selected skill belongs to Nexus’s writable skill root. Skills discovered from Agent Skills roots are intentionally read-only to the remove action; editing them remains possible through $EDITOR.

Source

pub fn start_skill_install(&mut self, spec: &str)

Domain half of /skills install: parse the typed owner/repo/path (or owner/repo) and kick off the background GitHub fetch. Same bg-task shape as memory extraction. The view owns the edit buffer and mode; Ok(()) means the task started (or the spec was invalid — the message is pushed as a status line).

Source

pub fn on_skill_install_result( &mut self, result: Option<Result<String, String>>, )

Source§

impl App

Source

pub fn snapshot(&self) -> Result<CoreSnapshot>

Serde-shaped state for API consumers (the Phase 4 host). Sessions come from the picker cache when loaded, else a fresh db read — a failed read is an error, never a silently-empty session list.

Source§

impl App

Source

pub fn switch_to_default_space(&mut self) -> Result<()>

Switch back to the default space (used when the active space is deleted from the picker — view layer calls this, so it’s pub).

Source

pub fn set_active_space(&mut self, row: SpaceRow)

Switch the active space, clearing the open conversation (a session belongs to exactly one space). The space picker’s confirm (view layer) calls this after closing its popup.

Source

pub fn instructions_path_for_space(&self, name: &str) -> Option<PathBuf>

Path to the highlighted space’s instructions file, creating a stub with a short header comment if it doesn’t exist yet (so $EDITOR has something to open). The picker cursor lives in the view layer; callers pass the selected space’s name.

Source

pub fn memory_path_for_space(&self, name: &str) -> Option<PathBuf>

Path to the highlighted space’s memory file (the numbered facts a conversation in that space has accumulated), creating an empty stub with a header comment if nothing’s been extracted yet.

Source§

impl App

Source

pub fn toggle_swarm_mode(&mut self) -> Result<()>

Flip swarm mode for the active session.

Source

pub fn start_swarm_turn(&mut self)

Start a swarm turn for the just-sent message (already pushed to self.messages/the db by send_message). No-op if one’s running.

Source

pub fn save_swarm_roster(&mut self) -> Result<()>

Domain half of the roster save: drop blank rows and persist. The view clamps its cursor after calling this.

Source

pub fn stop_swarm(&mut self)

Stop the running swarm immediately. Persona model/tool streams are children of the aborted orchestration task and are dropped with it. The view closes its popup after calling this.

Source

pub fn on_swarm_update(&mut self, r: Option<(String, SwarmUpdate)>)

A swarm turn update: persist it, and mirror it into the live transcript if the session it belongs to is the one being viewed. None = the job’s channel closed (fires once, right after the last update).

Source§

impl App

Source

pub fn save_clipboard_image( &mut self, width: usize, height: usize, bytes: &[u8], ) -> Option<String>

Save a clipboard image to the space’s images dir and return a markdown snippet ![pasted image](filename.ext) that can be inserted into the composer text.

Source

pub fn cleanup_incognito_images(&mut self)

Remove the incognito temp image directory if it exists.

Source§

impl App

Source

pub fn load_usage(&self) -> UsageData

Load the aggregates for the currently selected range (the view owns the cursor; the range is a persisted core preference).

Source

pub fn persist_usage_range(&mut self)

Domain half of the range cycle: persist the choice (the view switches usage_range and reloads).

Source

pub fn backfill_usage_costs(&mut self)

Recompute historical costs from the current catalog before rendering — rows logged before pricing existed stay accurate. Called on popup open and refresh.

Source§

impl App

Source

pub fn create_watch(&mut self, topic: &str)

/watch <topic> with no existing watch of that exact topic in this space: create one (fixed 24h interval) plus its own research session, and kick off the first run immediately (ungated).

Source

pub fn delete_watch(&mut self, id: &str) -> Result<bool>

The watch picker’s confirm/delete flows live in the view layer; this is the delete half: drop the row from the db and refresh the cache. Returns whether a row existed.

Source

pub fn run_due_watches(&mut self)

Startup hook: re-run every due watch (across all spaces) in the background, ungated. Best-effort — a watch whose research job can’t start (e.g. no model configured) is silently skipped; it’ll be retried on the next app open since last_run_at isn’t touched.

Source

pub fn run_one_watch(&mut self, w: &Watch) -> bool

Start one watch’s research job (due or not — nexus watch run <id> force-runs). A watch may belong to a space other than whatever’s currently active — start_research_with_gate reads self.active_space for the toolbox/file paths and save_research_report’s destination, so it must be switched to the watch’s own space for the run, same as its session. Returns whether a job actually started.

Source

pub fn previous_citations_for_watch_session( &self, session_id: &str, space_id: &str, ) -> Result<Option<Vec<String>>>

Some(urls) if session_id is a watch’s session and it has a prior run (citations already indexed from an earlier save_research_report call); Ok(None) for a first run or a non-watch session — either way means “no diff section”. Takes the report’s own space_id rather than reading self.active_space: this runs from on_research_done, which fires asynchronously and may land well after the user (or run_due_watches, which restores it right after spawning the job) has switched the active space away from the one this job actually ran in.

Source§

impl App

Source

pub fn new(db: Db, key: Option<&str>, space: Space) -> Self

Source

pub fn refresh_toolbox(&mut self)

Rebuild the toolbox from the current searxng_url, so a settings change takes effect immediately (no restart). Web search tries the configured backends first and has keyless HTML fallbacks.

Source

pub fn is_research_session(&self) -> bool

Whether the active session is a research session.

Source

pub fn blocked_domains(&self) -> Vec<String>

The active space’s always-excluded search domains, from its blocked_domains.txt (comma-separated; missing file = none).

Source

pub fn init(&mut self)

Kick off the initial model fetch if a key is already present. Call once after construction, from within the tokio runtime.

Source

pub fn spawn_update_check(&mut self)

Kick off the startup update check: once per day, compare the newest published version against this build and auto-install it when a newer release exists. Best-effort — offline or a failed fetch is silent. Spawned before the event loop starts; the result arrives as AppEvent::UpdateCheck.

Source

pub fn on_update_check(&mut self, latest: Option<String>)

Handle the startup update check result: when a newer version is published, auto-install it via cargo in a detached background process when the environment allows (dev builds, missing cargo, and opted-out installs fall back to a plain notice). None (failed check) and same/older versions are silent — the check is a courtesy, not a nag.

Source

pub fn is_streaming(&self) -> bool

Source

pub fn chat_task_count(&self) -> usize

Source

pub fn chat_task_for_session(&self, session_id: &str) -> Option<&ChatTask>

Source

pub fn active_chat_task(&self) -> Option<&ChatTask>

Source

pub fn active_streaming_text(&self) -> Option<&str>

Source

pub fn viewing_stream(&self) -> bool

True when the in-flight stream belongs to the active session (every stream carries its origin session_id, so this is exact).

Source

pub fn is_welcome(&self) -> bool

The empty start screen (banner + greeting + clock) shows when there’s no conversation yet — a stream running in another session doesn’t hide it.

Source

pub const fn tick_spinner(&mut self)

Advance the spinner one frame (called on the animation tick).

Source

pub const fn spinner_char(&self) -> &'static str

Current spinner glyph.

Source

pub fn spinner_color(&self) -> SpinnerColor

Randomly-chosen spinner colour for the current response.

Source

pub fn thinking_phrase(&self) -> &'static str

Present-tense phrase for the in-progress response (“Vibing”).

Source

pub fn thinking_text(&self) -> Option<&str>

Reasoning tokens accumulated so far this stream, if any.

Source

pub fn push_status(&mut self, s: impl Into<String>)

Queue a one-line status update as AppEvent::Status. The 2e view layer keeps its own status field, fed by these events; headless consumers track them locally.

Source

pub fn push_composer_set(&mut self, text: impl Into<String>)

Ask the view to replace its composer contents (a send-failure path restoring the user’s message, a gate reply rolled back, …).

Source

pub fn push_composer_clear(&mut self)

Ask the view to clear its composer.

Source

pub fn push_viewport_reset(&mut self)

Ask the view to reset its viewport state (scroll, selection, pinning baseline) — pushed wherever domain code switches sessions, starts a stream, or otherwise invalidates the rendered conversation.

Source

pub fn push_history_invalidated(&mut self)

Ask the view to rebuild its wrapped-history render cache (in-place message edits would otherwise leave stale wrapped content).

Source

pub fn pop_pending_event(&mut self) -> Option<AppEvent>

Pop one locally-queued event (status lines, gate arming, composer feedback). The view drains these before every draw so status changes land on the same frame as the action that caused them; next_event drains any remainder before blocking on the channel sources.

Source

pub async fn next_event(&mut self) -> AppEvent

Next background event from either the streaming task or a model fetch. Pends on an idle source, so it only resolves when something happens.

Source

pub fn on_models_result(&mut self, result: Option<Result<Vec<Model>, String>>)

Source

pub fn vlm_ocr_enabled(&self) -> bool

Whether scanned PDFs should OCR through the OpenRouter vision model: explicit “vlm”, or “auto” with an OCR model configured. (“local” and “tesseract” route elsewhere.)

Source§

impl App

Source

pub fn cancel_chat_tasks(&mut self)

Abort every interactive chat task before the TUI exits. Chat streams are intentionally not persisted or resumed across process restarts.

Auto Trait Implementations§

§

impl !Freeze for App

§

impl !RefUnwindSafe for App

§

impl !Sync for App

§

impl !UnwindSafe for App

§

impl Send for App

§

impl Unpin for App

§

impl UnsafeUnpin for App

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more