Cumora 0.4.0
A fifth BYOA engine, the dark theme finished off, and one more lock on attachment reads.
OpenCode engine (#74, @KIDA-MNESIA)
OpenCode joins Claude Code, Codex, Grok Build and Cursor Agent as a BYOA brain. Full wakes run opencode run --format json --auto, continue the emitted session id on the next wake, and record each step_finish as a ledger hop, so OpenCode spend shows up in observability alongside every other engine.
Triage is deliberately not the same command: it runs through an injected cumora-triage agent whose wildcard tool permission is deny, and never receives --auto. The small brain judges; it cannot act. CUMORA_DEFAULT_OPENCODE_MODEL sets a deploy-level pin and CUMORA_OPENCODE_ARGS is the escape hatch, matching the other adapters.
Attachment reads can't follow a symlink out (#73, @KIDA-MNESIA)
0.3.0 stopped attachments from selecting an arbitrary fetch target, but the local read still proved containment lexically — a link anywhere under the upload root would have been followed. Reads now resolve the physical path of both the root and the target before comparing, open with O_NOFOLLOW so the file can't be swapped in between, and accept regular files only. No remote way to plant such a link was found; this closes the door regardless.
Dark theme, the rest of it (#72, @kelvinSu0505)
Shipping contracts, the document toolbar, kanban columns and cards, the assignee picker and the profile provider chip were still painting hardcoded light surfaces, so dark mode kept a few frozen white sheets. They now read the same tokens as everything else, and the five native <select> controls in the contract workspace became the Cumora Select — the only way to theme a dropdown at all. Light values are the original hex, byte for byte.
Capacitor (iOS/Android) still hasn't been exercised against the dark palette.
Desktop app auto-updates. BYOA hosts want npm i -g cumora@latest plus a daemon restart to pick up OpenCode.