Skip to content

Local Waifu 0.1.17

Choose a tag to compare

@lumizone lumizone released this 21 May 09:43
· 4 commits to main since this release

Second internal-quality release in 24 hours. A 5-agent bug hunt
swept the chat hot path, security surfaces, lifecycle / sidecars,
frontend, and external integrations. Every fixable finding landed.

Fixed (privacy / security)

  • Offline-mode bypass. When offlineMode=true was set and the
    local Ollama crashed mid-conversation, the cloud-fallback path
    would silently forward your message to BYOK Anthropic / OpenAI /
    DeepSeek anyway — directly contradicting your privacy opt-out.
    The offline check is now re-validated before any fallback.
  • Soul-file tmp 0600. The encrypted tmp file the soul saver
    writes (before atomic-rename) now has 0600 perms from creation
    rather than briefly landing at the umask default of 0644.
  • Error-body sanitisation gaps. Three cloud / BYOK error paths
    (Anthropic mid-stream SSE error, Dodo deactivate failures, the
    byok_test detail field) were passing raw HTTP body text
    through to the UI. Each now runs through the same token-masking
    sanitiser the other cloud error paths use.

Fixed (chat correctness)

  • Surprisal gate firing every turn. The "is this user message
    novel enough to trigger KG extraction?" check compared a
    weighted-by-importance-and-recency similarity against a 0.78
    threshold designed for raw cosine. Result: aged memories always
    scored under threshold, and KG extraction fired on every chat
    turn for users with any history. Now compares raw cosine — only
    fires when the user is bringing up something genuinely new.
  • Orphan user messages. The chat pipeline writes the user turn
    to the DB before the LLM call so it survives crashes mid-stream.
    Downside: an actual crash (or ?-propagated error) between the
    user write and the assistant write left a dangling user row
    with no reply, rendering as a lonely user bubble forever. A
    startup sweep now deletes any user row older than 1 hour that
    has no character row following it — preserves recent legit
    messages, cleans up genuine orphans.
  • Empty model routing. route("openai:") (and the other
    cloud prefixes) used to send an empty model field to the
    provider and get a 400. Empty model after a prefix now falls
    back to the Ollama path so the standard error surface kicks in.

Fixed (lifecycle / data integrity)

  • Backup integrity. backup_app_data now runs
    PRAGMA wal_checkpoint(TRUNCATE) before the file copy — the
    previous version copied the live WAL alongside the main DB
    with no synchronisation, so a write committing mid-copy could
    produce a backup that refused to open.
  • Cmd+Q sidecar shutdown. Cmd+Q on the macOS menu bar used
    to bypass both the WindowEvent::Destroyed handler (intercepted
    by hideToTray) and the tray Quit handler — leaving Ollama and
    the TTS sidecar as zombie child processes. A new
    RunEvent::Exit handler now catches every exit path and
    shuts down sidecars with a 5 s deadline.
  • post_update_finalize warning loop. A crash between the
    version-marker write and the soul-file check used to hide any
    warning forever. The check now runs before the marker, and the
    marker is only set when the check passes — or after 2 retries
    with a separate counter, so a genuinely missing soul doesn't
    loop the warning forever either.

Fixed (resource exhaustion)

  • Swift script timeouts. Calendar, Reminders, and OCR all
    shelled out to /usr/bin/swift with no deadline. A hung Swift
    process (TCC dialog blocked, missing dylib, signal-stopped)
    would pin a Tokio blocking-pool thread until the OS reaped the
    process — and enough hung calls could starve every other
    blocking caller. All three paths now route through a shared
    watchdog with a 15 s deadline (kills the child + clear error).
  • Image download cap. The fal.ai and OpenAI image-URL
    download paths called .bytes().await with no content-length
    check, so a misbehaving CDN streaming gigabytes would OOM the
    app. Both now cap at 20 MB.

Fixed (frontend stream race)

  • Stop button leaving markers visible. Hitting Stop mid-stream
    after the assistant bubble appeared but before done arrived
    used to leave the partial tokens (with any [[MEM ...]]
    bookkeeping markers) frozen in the bubble. The bubble now still
    receives the cleaned text on done even when stopped — so the
    final state matches what the server actually committed.
  • Character switch race. Switching characters while a stream
    was in flight relied on bumpStreamGeneration to drop later
    tokens, but a done event arriving in the microtask window
    before clearMessages could still write into the old
    character's array. Now uses stopStreamingMessages for an
    atomic bump-and-finalise.

Audit deferrals

  • Soul-file double-PBKDF2. Cocoon::new(key) runs its own
    100k-iteration PBKDF2 on top of our already-PBKDF2'd master key,
    for 200k iterations total per soul read/write. Switching to
    MiniCocoon::from_key would skip cocoon's KDF but the wire
    format is incompatible — would brick every existing user's
    soul files. The performance cost (~50 ms per character switch,
    never on the chat hot path) is acceptable; a format-version
    byte for future migration is tracked as v0.2 work.