The plugin ships two module entries: the server plugin (registered in
opencode.json) and the TUI plugin (./tui subpath, registered in tui.json).
opencode's TUI loader reads tui.json, not opencode.json, so a fresh install
that only writes opencode.json leaves the Roles - Models sidebar section
and TUI-only commands unloaded.
Commit 19e8cab717 fixed the install flow, but existing users who installed
before that won't have tui.json populated and have no signal that anything
is wrong. This adds a doctor check that detects the mismatch and emits a
clear warning with a one-line fix suggestion (re-run the installer or add
the entry manually).
The check is a soft warning, not a fatal blocker, and is pure (no side
effects, no auto-write). It accepts:
- canonical and legacy package names in either config
- file: URLs pointing at a local checkout of our package (opencode-tui
loads the ./tui subpath via package.json exports for those entries)
Status matrix:
- both registered -> pass
- server registered, TUI missing -> warn
- TUI provided via file: entry -> pass
- neither registered -> skip (plugin not installed at all)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
processingErrors was emptied in a finally block, so a second
session.error fired for the same assistant message id after the
first recovery resolved would re-run abort, history fetch, the
recovery toast, and any auto-resume promptAsync (resumeSession),
producing duplicate internal prompt injections during stale event
re-emission or polling-driven retries.
Drop the in-flight delete and keep the dedupe permanent for the
plugin lifetime. A genuinely new failure starts a new assistant
message with a different id, so this never blocks future legitimate
errors. Same-id duplicates collapse into a single recovery attempt.
Add hook.test.ts asserting that two sequential handleSessionRecovery
calls for the same recoverable info trigger session.abort, the
recovery toast, and any internal promptAsync at most once.
Continuation injector previously called normalizeAgentForPromptKey,
which collapsed agent inputs like "Sisyphus - Ultraworker" or
"sisyphus" down to the lowercase config key (e.g. "sisyphus").
OpenCode's promptAsync rejects that with "Agent not found", so the
ralph-loop continue prompt silently failed to dispatch on parent
sessions whose inherited message used a known display name.
Switch to normalizeAgentForPrompt and add a small wrapper that:
- preserves any agent string already in canonical " - " display form
verbatim (covers ZWSP-prefixed inheritance and user-defined custom
agents)
- otherwise normalizes config keys / legacy parenthesized names to the
registered display name OpenCode expects.
Update the existing regression tests so that ZWSP-prefixed and clean
inherited agents both assert the registered display name reaches
promptAsync.
Keep prompt reservations briefly after successful dispatch so rapid idle/message/error transitions cannot inject the same follow-up twice.
Route all production session prompt calls through the shared gate, restore skipped background resume state, release holds after abort/recovery paths, and preserve Ralph/ULW loop state when a dispatch is deferred.
Add regression coverage for session routing, static prompt route auditing, team-mode live messaging, model suggestion retries, call-omo-agent reuse, background parent wakes, runtime fallback, compaction recovery, Atlas, and Ralph/ULW loops.