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.
When many background tasks complete in rapid succession while the parent
session is idle, each completion fired its own promptAsync call, stacking
N consecutive `<system-reminder>` user messages with no assistant turn
between. Hyperplan + many parallel explore subagents made this very
visible to the user.
Route the idle-path through the existing pendingParentWakes queue with a
100ms debounce window. Notifications arriving during the debounce join
the same batch, the 150ms settle window also coalesces newcomers, and a
single batched prompt fires to the parent. Busy-path semantics are
unchanged (still 1s retry).
Prior attempts (1c05c60dc, ea55c385b, a337635e3) all coalesced only the
busy-defer path, leaving the idle-immediate-send path uncoalesced.
Reframe the skill's Lifecycle section so the lead treats teams as
ephemeral, one-per-phase units. The moment a phase ends or the shape
no longer fits, call team_delete and spawn a fresh team. Restructure
through delete-then-create, never in place.
Also fixes a misframing in old step 5: team_shutdown_request is a
per-session self-shutdown signal, not a 'wind down the team' command.
team_delete is what tears the whole team down.