Status: Current — roadmap reflects the delivered milestones and the paused remainder
Treat this as a large multi-file feature:
- develop on a dedicated product branch
- split milestones into independently reversible commits
- keep upstream integration points small
- validate the uncertain runtime pieces before workspace UI expansion
- update documents together with behavior changes
origin = product fork
upstream = anomalyco/opencode
Suggested long-lived branches:
upstream-dev mirror/reference for upstream dev
wife/main stable product integration
Feature branches (short names, no type prefixes — see AGENTS.md):
project-binding
wife-chat
activity-pipeline
persona-director
voice-runtime
runtime-hardening
workspace-extensions
Use Conventional Commits and keep commits independently revertible.
Status: delivered on
wife-baseline(commitsfeat(wife-core)…,feat(app): add wife mode setting and toggle,chore(i18n)…,fix(app): restore custom-elements reference directive,feat(wife): add gated event bridge provider).
Deliverables:
- fork builds and launches Desktop
- Classic Mode switch (app setting
general.wifeMode, default off, settings UI section) - dedicated Wife module directories (
packages/wife-core,packages/app/src/features/wife/) - minimal event bridge with no behavior change (per-server
wife.activitydebug logging, gated by the flag) - logging namespace (
wife.*from@opencode-ai/wife-core/log) and feature flag
Acceptance:
- upstream behavior remains unchanged with Wife Mode disabled
- disabling Wife Mode does not start Live2D or TTS resources
Status: delivered on
character-registry+wife-settings. The original linear import wizard was replaced by an instant "Add character" flow plus a per-character settings page (avatar / general / semantic mapping / danger zone).
Deliverables:
- import character package or Live2D model
- asset validation
- motion/expression/parameter scan
- preview player (static avatar picker; live WebGL preview in Phase A)
- semantic mapping UI (collapsible groups, narrow rows)
- persisted character definition and capability cache
Acceptance:
- user registers a model without hand-editing JSON
- missing mappings use visible fallbacks
- invalid imports do not crash the app
Implementation notes (delivered on wife-settings):
- model references resolve relative to the
.model3.jsondirectory; VTS-style loose.motion3.json/.exp3.jsonfiles are discovered when the model declares none; missing optional assets warn instead of block - mapping suggestions match English and CJK names;
custom.*gestures/emotions are type-level only (UI pending) - Settings → Wife sidebar category with General / Characters tabs; i18n policy: en + zht translated, other locales carry English placeholders with
// TODO: translate via translate:appcomments untiltranslate:appis run
Status: delivered on
live2d-runtime. Runtime =pixi.js@7+pixi-live2d-display-lipsyncpatch(cubism4 entry); Cubism core downloaded at build time byscript/fetch-cubism-core.tsand bundled (users download nothing); desktop reads model folders via a native dialog IPC (wife-pick-model-folder, which also scans the folder and whitelists it in Electron Main) +wife://protocol (privileged scheme withcorsEnabled); panel is a right-side split between the conversation and the review panel (chat | wife | review), toggled by a Toggle Wife button next to Toggle Review (keybindmod+alt+w); web build shows an empty state in the panel (desktop-first). The panel has a manual intent test strip (state / gesture / emotion) drivingapplyIntentuntil the activity pipeline lands.
Commits:
feat(wife): add live2d runtime dependencies and cubism core fetch script
feat(wife): add wife panel state and toggle button
feat(desktop): serve model files via wife protocol
feat(wife): add live2d view and wife side panel
fix(desktop): keep nested model file paths relative to the picked folder
fix(wife): load cubism4 runtime entry and guard lazy import failure
fix(wife): load cubism core in desktop renderer and reserve panel width
fix(desktop): enable CORS for the wife protocol
Acceptance:
- the model renders in the wife panel and plays mapped motions/expressions
- toggling the panel behaves like the review panel
- web build shows an empty state for the panel (desktop-first)
Small UX follow-up completed on live2d-runtime:
- resizable wife panel width:
layout.wife.width(persisted, default 320, min 260 / max 480),ResizeHandleon the panel's left edge, chat column absorbs the change (existingsessionPanelWidthcalc) - mouse-wheel zoom on the Live2D canvas:
zoomFactormultiplied into the fit scale (0.2x–3x, runtime-only, multiplicativeexp(-deltaY * 0.001), centered on the viewport)
Commits:
feat(wife): make wife panel width resizable
feat(wife): add wheel zoom to live2d view
Status: delivered ahead of
v0.1.0-alpha.1. Detail in 12-handoff.md.
chore(brand): fork identity — product name, app IDs,opencode-wife://deep link, OW icon set,NOTICE.mdfeat(desktop): share opencode agent state — separate Wife profile, shared Agent state, mutual single-instance exclusionfix(desktop): guard shared data compatibility — base version1.18.14, read-only schema check, consistency backupsfeat(desktop): import opencode preferences — idempotent first-run import + Settings re-importfix(desktop): isolate fork updates — updater disabled, no upstream publish referencesfeat(desktop): add live2d runtime setup — user-supplied Cubism Core wizard replacing the build-time fetchdocs: public documentation — root README (en/zh-TW/zh-CN), docs reorganization, SECURITY.mdci(release): Windows x64 alpha workflow — candidate artifacts and draft prereleases
The roadmap was reordered during design review: the Wife Assistant Chat is the most visible interactive surface and has no hard dependency on the activity pipeline, so it moves ahead of it. Voice is deferred until the character has something to say and the assistant has text output (speech-bubble fallback covers the gap). Two independent loops share one presentation runtime:
Loop A (observation): events → activity store → trigger policy → persona → intent
Loop B (assistant): user ⇄ wife session (read-only) → reply → intent
both → presentation coordinator → Live2D + bubble + speech queue
A second, restricted conversation surface inside the wife panel. The assistant watches nothing yet (that is Milestone 4); it answers project questions with read-only tools.
Status: Side Chat core delivered on
live2d-runtime. Each main session owns a persistent, archived Wife session with a verified deny-all/read-glob-grep permission profile. The overlay restores history, survives panel collapse, and isolates stop/error state. Character persona, automatic current-Agent context,/send, and/clearare delivered. Chat-driven presentation intent is paused with the downstream milestones.
Deliverables:
- wife session management: legacy API session (
POST /session+ prompt) with apermissiondeny ruleset — read-only profile deniesbash/edit/write/apply_patch/task/skill, keepsread/glob/grep; shares the active provider/model configuration; no permission prompts - session-level conversation memory (one persistent Wife session per main session; switching restores the matching history)
- context management: server-side auto-compaction (existing feature — no custom truncation)
- chat UI: the Live2D canvas hosts the conversation overlay, reuses
PromptInputV2(controller mode) with a custom wife submit handler, lightweight message bubbles (reuseMarkdown), and asynchronously generated choices rendered as buttons (visual-novel style; clicking sends the chosen text) - speech bubble overlay on the Live2D canvas for chat replies
- global character chat persona: user address plus bounded free-form speaking/personality instructions, applied from the next reply
- automatic current-Agent snapshot: up to 80 recent messages projected into a 12,000-character untrusted read-only reference
/send: a temporary archived deny-all/no-tools session summarizes the Side Chat and latest Agent snapshot into the scoped Agent composer; existing drafts offer Replace / Append / Cancel and are never auto-submitted/clear: confirmed permanent deletion of the current Wife session and pointer while preserving character, model, persona, compatibility, and Live2D preferences- configurable reply-choice generation: a global enable switch plus independent model/variant, defaulting to
opencode/deepseek-v4-flashatlow; each successful reply runs in a temporary archived deny-all session without blocking bubble reveal
Acceptance:
- asking project questions returns useful answers; no write/exec tool is available to the wife session
- choices render as buttons and submit their text
/sendproduces an editable Agent task without auto-submitting or crossing session boundaries/clearremains cleared after restart; its next normal message creates a fresh Wife session- memory is session-scoped; a collapsed panel does not interrupt an in-flight reply
- model failure shows an error state without affecting the agent session
Status: paused while Side Chat remains the product mainline.
Deliverables:
- global default character
- project character selection
- active-tab character switching
- session temporary override
- per-project speech policy
Acceptance:
- two projects can use different characters
- switching tabs selects the correct character
- configuration stores references, not duplicated assets
Status: paused.
The character starts watching the agent work. Observation is event-driven and intentionally cheap.
Deliverables:
- event normalizer: OpenCode events → stable wife event contract (pure functions)
- session activity store: semantic snapshots (current task, phase, status, recent actions, permission/question state), capacity-bounded — the observation context is a small snapshot, never a transcript
- trigger policy: cooldowns, deduplication, background-session policy
- observation state machine:
observing⇄idle(idle after 5 minutes without events) andstopped(panel collapsed or Wife Mode off — full stop, including activity updates; resumes from scratch) - template bank: situation key → sentence list, random pick without immediate repeats; per-character
speechTemplates+ built-in defaults (e.g. long-idle: "好好休息一下,我會在這邊等你的"); settings UI editor - idle behavior: random idle motions, occasional idle-template speech (30-minute cooldown, configurable)
- presentation coordinator v2: merges event-driven and chat intents with arbitration (speech/gesture priority)
- manual intent test strip stays as a debug surface
Acceptance:
- routine tools do not generate speech
- permission and completion are presented once
- the character goes idle after inactivity and pauses when the panel is collapsed
- background progress does not take over the active character
Status: paused. This is separate from the delivered static character chat persona.
Deliverables:
- structured persona request/response via
LLM.generateObject(text + emotion + gesture + optional choices) - validation and stale-result cancellation
- recent-speech deduplication
- configurable progress triggers
- semantic gesture/emotion selection
Acceptance:
- spoken lines are concise and supported by activity data
- model failure uses templates
- AI never outputs asset paths or tool decisions
Status: paused.
Deliverables:
- GPT-SoVITS engine definition
- managed process or endpoint connection
- voice preset registration
- streaming synthesis
- renderer audio playback
- volume-based lip sync
- test sentence UI
Acceptance:
- one sentence plays with synchronized mouth movement
- engine crash does not affect OpenCode
- speech can be cancelled
Deliverables:
- process restart policy
- diagnostics page
- performance limits
- queue expiry and interruption behavior
- multi-window ownership
- end-to-end tests
Acceptance:
- no duplicate speech across windows
- repeated TTS failures degrade cleanly
- long coding sessions do not leak unbounded activity data
Only after the character pipeline is stable:
- generalize utility tabs
- create pane layout tree
- move tabs between panes
- persist layouts
- detach tabs into new BrowserWindows
- support a dedicated Live2D tab/window
These changes touch high-conflict upstream UI files and should remain separate from the character runtime milestones.
| Area | Decision |
|---|---|
| Wife session | Legacy API (POST /session + prompt); read-only profile via permission deny ruleset; { permission: "*", action: "deny" } gives a no-tools mode |
| Structured output | LLM.generateObject (choices / persona) |
| Choices | Renderer-rendered buttons from structured output; clicking sends the chosen text (no server question API) |
| Input reuse | PromptInputV2 (controller mode) with a custom wife submit handler |
/send |
Temporary deny-all/no-tools summary session; scoped editable Agent draft; never auto-submit |
/clear |
Permanently delete the assistant session and pointer after V2 confirmation |
| Memory | Session-scoped, keyed by session id, pruned |
| Context management | Wife memory uses server auto-compaction; current Agent context is a non-persisted bounded projection |
| Observation | Event-driven; activity snapshot input (small window, bounded); 5-minute idle threshold; 30-minute idle-speech cooldown; full stop when collapsed / Wife Mode off |
| Template bank | Per-character speechTemplates (sentence lists) + built-in defaults |
| Panel width | Resizable 260–480 px, default 320, persisted |
| Zoom | Wheel zoom 0.2x–3x, runtime-only, centered |
| Security | Permission ruleset is the primary boundary; renderer never renders write/exec tools; in-flight chat replies survive panel collapse |
Run focused checks when changing shared logic or completing a milestone slice:
- schema/unit tests
- reducer/trigger-policy tests (normalizer, trigger policy, coordinator arbitration — pure functions)
- Live2D import fixture tests
- speech queue tests
- relevant renderer tests
Run once:
- full typecheck
- lint
- unit tests
- Desktop build
- packaged smoke test where relevant
- acceptance criteria pass
- no swallowed errors
- configuration migration is defined
- related documentation is updated
- commits are clear and independently revertible
- upstream-facing files contain only necessary integration changes