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.
Handle OpenCode session events that carry the session ID under properties.info.id or properties.info.sessionID so background tasks and continuation hooks do not miss idle/error/delete events.
Add regression coverage for nested session.idle events completing background tasks and waking continuation hooks.
Only clear recentAnyIdles when the stored marker matches the synthetic idle timestamp for the same session, preventing accidental clobbering of newer idle markers. Add a regression test to verify other sessions keep their dedup state during this bypass path.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
When session.status(idle) is converted to synthetic session.idle and recorded in recentAnyIdles, a real session.idle arriving within 500ms was being dropped by the dedup logic. recentSyntheticIdles was cleared but recentAnyIdles persisted, causing TODO-DIAG to red-alert with 'no todossession.idle event'.
Fix: when real session.idle arrives, also clear recentAnyIdles entry so dedup does not drop it. Test renamed and expected dispatchCalls updated 1 → 2.
Fixes#2667
Uses new Function() to prevent the static ESM loader from seeing
the bun: protocol import at parse time. In Electron/Node.js, the
import() call is evaluated at runtime and gracefully returns null
when bun:sqlite is unavailable.
Fixes#3829, likely fixes#3762
Adds keyword_detector.disabled_keywords config so users can opt out of
specific keyword detectors individually without disabling the entire
keyword-detector hook. Allowed values: 'ultrawork', 'search', 'analyze',
'team'. Default empty/missing -> all four detectors active (no behavior
change for existing configs).
Motivation: an audit revealed search and analyze patterns trigger on
~30-60% of normal conversational user messages (e.g. 'how to', 'why is',
'show me', '왜', '어떻게'). The disable list is the immediate kill switch
while the patterns themselves are tightened in a separate PR.
Schema follows the existing per-feature config block convention shared
by team_mode, ralph_loop, runtime_fallback, and comment_checker. The
KeywordType enum (z.enum) lives next to the config schema and is
re-imported by the detector to keep the union type in lockstep with the
schema.
Threading:
pluginConfig.keyword_detector
-> create-transform-hooks.ts (factory wiring)
-> createKeywordDetectorHook(config)
-> detectKeywordsWithType(text, agent, model, disabledKeywords)
-> Set-based filter at the source-of-truth detector
Adds 8 regression tests covering per-keyword disable, multi-keyword
disable, partial disable (one keyword off, another still firing),
ultrawork toast suppression, undefined config, and empty array.
Top-level `import { Database } from 'bun:sqlite'` caused Node/Electron's
ESM loader to reject the plugin at module-graph resolution time because
the `bun:` protocol is not in Node's allowed scheme list. This prevented
the OpenCode desktop app from loading the plugin at all.
Fix:
- Remove top-level static import of `bun:sqlite`
- Use a lazy importer (`_bunSqliteImporter`) that calls
`import('bun:sqlite').catch(() => null)` at runtime
- If the import returns null (non-Bun environment), log a warning and
return early — no DB override attempted, plugin loads normally
- Expose `__setBunSqliteImporterForTesting` / `__resetBunSqliteImporterForTesting`
test seams to verify the Node/Electron fallback path
Closes#3795