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
Previously each transform hook was awaited sequentially without per-hook
error handling. If contextInjectorMessagesTransform or thinkingBlockValidator
threw, toolPairValidator was silently skipped, leaving orphaned tool_use
blocks in the post-compaction API payload and producing
"messages.N: tool_use ids were found without tool_result blocks immediately
after" 400s from Anthropic.
Wraps each hook in runHookSafely so an upstream throw is logged but the
chain continues. Adds regression tests covering the isolation contract and
the consecutive-assistants compaction tail case (ses_22bd806).
The sort shim from the previous commit enforces canonical core ordering at runtime, so ZWSP prefixes are no longer needed. Removing them eliminates the Bun.stringWidth vs terminal-width drift that broke the TUI status bar (#3259).
Drop AGENT_LIST_SORT_PREFIXES and getAgentRuntimeName from agent-display-names; switch all call sites to getAgentDisplayName. getAgentListDisplayName stays as a thin alias for external importers.
Keep stripInvisibleAgentCharacters and the ZWSP regex paths so legacy session state and configs from v3.14.0-v3.16.0 still resolve.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Oracle flagged a regression introduced in PR #3507 commit 21554be8:
event.ts routed session.error through tmux pane cleanup BEFORE the
existing session-recovery / model-fallback logic ran.
Problem: when session.error was recoverable (context window limit,
quota rate limit, provider fallback), the recovery/fallback code would
successfully continue the SAME session - but by then its tmux pane had
already been destroyed. User-visible symptom is exactly the original
complaint - 'screen appears but streaming stops working' after an
auto-retry.
Fix is the minimal revert: remove the onSessionError funnel from
event.ts and drop onSessionError from the manager. Fatal errors that
actually end a session still fire session.deleted, which continues to
trigger cleanup correctly. Non-fatal error streams stay attached to
the surviving pane.
Three defects observed with tmux.isolation="session" where the omo-agents
session was left with orphan fish panes after subagents finished:
1. cleanup() never ran 'tmux kill-session -t omo-agents'. If any pane
lingered (for example because opencode attach stayed blocked on SSE),
the isolated session survived process shutdown. Now we explicitly kill
the shared session through killTmuxSessionIfExists when isolation is
"session".
2. session.error events bypassed tmux cleanup entirely. Only session.deleted
closed panes, so any provider error that did not escalate into a delete
left the pane behind. Added onSessionError on TmuxSessionManager, wired
from plugin/event.ts, which funnels through the same onSessionDeleted
close path for tracked sessions only.
3. retryPendingCloses() only ran when a new session was created. If the
main process went idle after a failed close, the pending session stayed
pending forever. TmuxPollingManager now accepts the retry callback and
fires it on every tick, alongside the existing stability-based close
sweep.
Manager tests cover isolation=session kill invocation, inline/window
isolation skipping the kill, the onSessionError happy + untracked paths,
and an isolated-session kill failure that must not break cleanup.
Updates the canonical Anthropic Opus model in every fallback chain
(sisyphus, oracle, prometheus, metis, momus, visual-engineering,
ultrabrain, deep, artistry, unspecified-high), the unspecified-high
category default, the think-mode HIGH_VARIANT_MAP, the Claude Code
alias map, the claude-thinking legacy alias, the context-limit GA
regex, and event.ts fallback strings.
Widens supportsCachedAnthropicLimit to accept both claude-*-4-6 and
claude-*-4-7 so the 1M context cache still applies across the bump.
Regenerates the bundled model-capabilities snapshot from models.dev
and the model-fallback snapshot to match the new source output.
- #3354: Coerce data.name to String in loadSkillFromPath/loadSkillFromPathAsync
to prevent crash when YAML parses numeric skill names (e.g., name: 12306)
- #3416: Add required run_in_background parameter to all task() examples in
ultrawork prompts (default, gpt, gemini, planner) to match tool schema
- #3379/#3417/#3418/#3337/#3335: Strip ZWSP (U+200B) before agent name
comparisons in agent-tool-restrictions, sync-prompt-sender, tool-execute-after,
tool-execute-before, oracle-verification-detector, call-omo-agent,
recovery-prompt-config, and agent-variant to prevent ZWSP-prefixed display
names from breaking exact-match lookups
The dedup logic dropped real session.idle events when a synthetic
idle had fired within the dedup window, losing data that downstream
hooks needed. Now real idle events always reach dispatchToHooks while
synthetic duplicates are still dropped when a real event came first.
🤖 Generated with OhMyOpenCode assistance
https://github.com/code-yeongyu/oh-my-opencode
Extract hardcoded GPT apply_patch permission logic into a reusable module
to ensure consistent behavior across all agents. This prevents GPT models
from using the unreliable apply_patch tool while allowing other models.
- Add gpt-apply-patch-guard.ts with GPT_APPLY_PATCH_GUIDANCE and getGptApplyPatchPermission
- Update Hephaestus agent to use centralized permission logic
- Update Sisyphus-Junior agent to use centralized permission logic
- Update all GPT prompt builders to reference shared guidance constant
🤖 Generated with assistance of OhMyOpenCode
The /stop-continuation command was ineffective because the stop-
continuation-guard cleared its stopped state on the very next
chat.message event. Since any user message (including normal chat
after stopping) triggers chat.message, the continuation would
resume immediately.
Root cause: the chat.message handler called clear(sessionID) on
every user message, treating it as a 'user resumed work' signal.
But the user expects /stop-continuation to persist until they
explicitly start work again.
Changes:
- stop-continuation-guard chat.message: no longer clears stop state
- tool-execute-before: /start-work, /ralph-loop, /ulw-loop now
explicitly clear the stop state (so continuation resumes when
user intentionally restarts work)
- Updated and added tests: 12 pass (3 new), 125 related tests pass
Closes#3276
AGENT_LIST_SORT_PREFIXES prepended U+200B Zero Width Space characters
to the four core agent display names so they would sort ahead of user
agents in the Tab cycle. Two problems with that approach surfaced:
1. Some terminal emulators (Ghostty, certain Windows Terminal
builds) render ZWSP as a visible box or extra space, producing a
visible black gap in the status bar before "Sisyphus" and
misaligning the layout (#3259).
2. The prefixes leaked into the plugin API surface via config.agent
keys, breaking prompt_async consumers that received
ZWSP-contaminated agent names (#3238).
#3242 already removed every call site of getAgentListDisplayName() in
production code. That made the sort prefixes dead code: the constant
table was still defined but nothing read it. This PR finishes the
cleanup by:
- Deleting the AGENT_LIST_SORT_PREFIXES constant entirely
- Turning getAgentListDisplayName() into a thin alias over
getAgentDisplayName() for BC with external importers
- Keeping stripAgentListSortPrefix() as a legacy data migration for
users upgrading from v3.14.0-v3.16.0 whose config.agent keys may
still have ZWSP baked in from the old code path
- Documenting the history on stripAgentListSortPrefix() so future
maintainers understand why the stripper has to stay even after
the injector is gone
Sort ordering is preserved via JS object insertion order in
reorderAgentsByPriority() plus the `order` field it injects on the
four core agents. Both mechanisms are already in place and both
pre-date this PR; the ZWSP prefix was an older third layer that was
only meant to work around alphabetical sorting in legacy OpenCode
before the `order` field landed upstream.
Tests: 4445 pass, 0 fail. Added 3 new assertions to
agent-display-names.test.ts verifying that getAgentListDisplayName
returns plain names containing no zero-width characters. Updated
chat-message.test.ts to use a literal ZWSP string instead of the
helper so the defensive-strip path still has coverage.
Closes#3259