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.
Adds .sisyphus/rules/test-discipline.md, auto-injected by
rulesInjector hook whenever an agent reads or edits a test file
(**/*.test.ts, __tests__/**, src/testing/**, test-setup.ts,
script/run-ci-tests.ts).
Rules enforced:
- 'bun test' must pass in one process, one run, no isolation flags
- no setTimeout/sleep in test bodies unless time is the SUT
- event tests must subscribe-before-trigger with explicit timeout
- no .only/.skip/process-isolation to mask state leaks
- prompt-builder tests must assert behavior, not text snapshots
Also un-ignores .sisyphus/rules/ so project-scope rules can be
checked in while keeping .sisyphus/{draft,run-continuation,...}
ignored.
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.