Skip to content

feat(opencode-server): OpenCode v1.4.4+ compatibility — GlobalEvent SSE, async prompt queue, session sync fixes - #21

Merged
Leoyzen merged 62 commits into
develop/agenticfrom
feature/yuchen.liu/fix_opencode_version
Apr 19, 2026
Merged

feat(opencode-server): OpenCode v1.4.4+ compatibility — GlobalEvent SSE, async prompt queue, session sync fixes#21
Leoyzen merged 62 commits into
develop/agenticfrom
feature/yuchen.liu/fix_opencode_version

Conversation

@Leoyzen

@Leoyzen Leoyzen commented Apr 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Upgrade OpenCode server to v1.4.4+ protocol compatibility, fixing session synchronization, SSE event routing, and concurrency issues that caused deadlocks and data loss in subagent workflows.

Changes

OpenCode v1.4.4+ Protocol Support

  • Add GlobalEvent envelope model, factory, and ServerState integration for the new SSE protocol
  • Add OTLP telemetry compatibility sinks for v1.4.4+
  • Add global management routes (/global/health, /global/event, /global/sessions) for newer clients
  • Wrap heartbeat and session events in GlobalEvent envelopes with workspace routing
  • Add workspace_id to Session model for TUI session routing

Session Synchronization & Subagent Fixes

  • Fix child session project_id inheritance — subagents now correctly inherit parent project metadata
  • Fix agent_lock deadlock: hoist agent assignment before lock acquisition, skip lock for child session REST endpoints
  • Fix ContextVar reset on subagent exit that caused state leakage
  • Scope _step_finish_emitted to parent session only, preventing premature stream termination
  • Cancel pending questions on SSE disconnect and pending prompts on per-turn completion to prevent agent_lock deadlock
  • Handle RunAbortedError like CancelledError in message processing
  • Yield PartUpdatedEvent for subagent user message text parts

SSE & Event Protocol

  • Add SSE anti-buffering headers (X-Accel-Buffering: no, Cache-Control: no-cache) to event endpoints
  • Split question IDs from permission request IDs to avoid cross-contamination
  • Fix source_name vs source_type for ToolPart subagent metadata
  • Include title in SpawnSessionStart ToolPart metadata for TUI consistency
  • Use structured logging (get_logger) instead of logging.getLogger for Logfire compatibility

Storage Layer

  • Add project persistence support for OpenCode storage provider
  • Fix title persistence on session reload (sync DB title → metadata)
  • Resolve model_variants in title generation before infer_model
  • Generate title even when default "New Session" exists
  • Fix shared engine disposal: use direct engine in SQLModelProvider to avoid accidental dispose()
  • Use batch query in list_sessions instead of N+1 per-session loads

Async Prompt Queuing

  • Serialize async prompt dispatch with per-session queues to prevent UI blocking
  • Cancel session background tasks on abort
  • Unblock queued prompts on per-turn completion

Refactoring

  • Replace exhaustive match arms with SessionIdProperties base class for cleaner variant handling
  • Extract _event_to_dict helper and document shared-agent race conditions
  • Remove experimental workspace support (unused in v1.4.4+)
  • Add has_pending_permission property to avoid private attribute access

Tests (36 files, +7644 lines)

  • GlobalEvent SSE integration and backward compat tests
  • SSE compliance, anti-buffering headers, and stream cleanup tests
  • Session concurrency, cache/404, restart recovery, and title fix tests
  • Prompt async queuing and question abort regression tests
  • TUI routing and spawn session start tests
  • Storage: batch list, project methods, no-shared-dispose tests

Squash Merge Message

feat(opencode-server): OpenCode v1.4.4+ compatibility (#21)

Upgrade OpenCode server for v1.4.4+ protocol: GlobalEvent SSE envelopes,
OTLP telemetry sinks, and global management routes. Fix session sync issues
by ensuring child sessions inherit project metadata, hoisting agent
assignment before agent_lock, and scoping step-finish events to parent
sessions. Introduce per-session async prompt queuing to prevent UI blocking
and cancel pending questions on SSE disconnect to resolve agent_lock
deadlocks. Optimize storage with batch session loading and project
persistence. Add 7500+ lines of test coverage across 36 test files.

Fixes session deadlocks in subagent workflows, missing child session
messages in TUI, and title/state persistence on reload.

yankaifeng and others added 30 commits April 18, 2026 19:04
…exhaustiveness warning

- Add global_router to conftest.py app fixture for SSE integration testing
- Remove unused MemoryStorageProvider import from conftest.py
- Add logger.warning for unhandled event types in _extract_session_id wildcard case
…ption isolation

- Add SSEStream class and global_event_stream fixture to conftest.py
- Add parse_sse_event helper for SSE data line parsing
- Add exception isolation to broadcast_event: iterate copy, put_nowait, catch QueueFull
- Safe queue removal in _event_generator finally block with contextlib.suppress
- Add race condition analysis comment for on_first_subscriber callback
…iff.patch, workspace routing, session_id extraction

- Migrate top-level variant into model.variant for UserMessage/MessageRequest (backward-compat)
- Replace FileDiff.before/after with FileDiff.patch matching OpenCode v1.4.0+ schema
- Add WorkspaceInfo/WorkspaceConnectionStatus models and /experimental/workspace endpoints
- Add workspace field to GlobalEvent envelope for TUI routing
- Fix base_path to anchor on server working_dir, not agent env.cwd
- Extend _extract_session_id for 6 additional event types (22 total)
- Add /global/diagnostic and /global/routing-check endpoints
- Add DiagnosticResponse model
- Update tests for new models and routing behavior
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Keep OpenCode question prompts on the que_* namespace while preserving the legacy permission bridge through /question. The regression tests cover both the new question prefix and the fallback permission reply path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Keep /global/event fully envelope-shaped so 1.4.4+ clients keep consuming after idle timeouts. Normalizing the routing directory in ServerState keeps the envelope aligned with the TUI filter.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refresh the compliance suite for wrapped heartbeat events and normalized routing metadata. This keeps the regression coverage aligned with the updated /global/event contract.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…wrap()

Add explicit no-session case block in _extract_session_id for all 16 event types that have no session association (ServerHeartbeatEvent, ServerConnectedEvent, FileWatcherUpdatedEvent, FileEditedEvent, etc.), eliminating spurious logger.warning spam on every broadcast. Replace redundant json.loads(_serialize_event()) in GlobalEventFactory.wrap() with direct model_dump() for better performance.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Expand _extract_session_id tests: verify all 16 no-session events return None without warnings, add test for unknown event types still triggering the case _ warning fallback, update docstrings to reflect GlobalEventFactory.wrap() using model_dump directly.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ribute access

Add public has_pending_permission() method to OpenCodeInputProvider and use it in permission_routes.py instead of directly accessing _pending_permissions.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…t race

Refactor _serialize_event into _event_to_dict (dict) + _serialize_event (string wrapper) so GlobalEventFactory.wrap() and _event_generator avoid redundant serialization round-trips. Add docstring comments in session_routes.py noting the shared-agent singleton race condition for multi-session scenarios.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…torage

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Leoyzen and others added 25 commits April 18, 2026 23:18
… Logfire-compatible structured logging

config_routes.py was the only file using standard logging.getLogger(),
which doesn't accept keyword arguments like count=, error=, model=.
All other opencode_server files use agentpool.log.get_logger() which
returns a Logfire-compatible logger that supports structured kwargs.
Without Logfire active, the standard logger throws TypeError on kwargs,
causing 500 errors on GET /provider and GET /config/providers.
… for TUI routing

opencode 1.4.11 TUI workspace filter silently drops events whose workspace field does not match the active workspace. GlobalEventFactory was created with workspace=None, so all agentpool events were dropped when any workspace was active. Derive workspace as wrk_{project[:12]} to match the /experimental/workspace API id format.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…a for TUI consistency

ToolPart metadata was inconsistent between _process_spawn_start (missing title) and _process_subagent_event (has title). The TUI relies on metadata.title for subagent display, so the initial ToolPart created by SpawnSessionStart must include it.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…Part subagent_type

ToolPart input.subagent_type was set to source_type (generic category like 'agent', 'team_parallel') but opencode's TUI taskAgent() matches against the agent registry by name (e.g., 'explore', 'general'). Using source_name allows the TUI to display the correct agent name and color instead of a generic 'Agent'.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…on routing

opencode 1.4.11 TUI filters sessions by workspaceID. Without it, child

sessions created by subagent spawning are invisible in the TUI session

list and the subagent view is not clickable.

- Add workspace_id: str | None = None to Session model

- Set workspace_id=f"wrk_{project_id[:12]}" in all 3 creation sites:

  session_routes.py create_session(), fork session, state.py ensure_session()

- session_data_to_opencode() derives workspace_id from metadata or project_id

- opencode_to_session_data() persists workspace_id in metadata dict

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- test_ensure_session_creates_new_session: assert workspace_id == wrk_global

- test_ensure_session_returns_existing_session: include workspace_id in mock

- test_spawn_start_child_session_has_workspace_id: new test verifying child

  sessions created by SpawnSessionStart have correct workspace_id derived

  from project_id

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…d ContextVar reset

- SessionManager.create_child_session() now inherits project_id and cwd
  from parent session, preventing TUI workspace filter from dropping
  child sessions that fell back to 'default'/'global' project IDs
- ACPSessionManager.create_session() now computes project_id from cwd
  using compute_project_id(), fixing ACP sessions with None project_id
- session_data_to_opencode() converter recomputes project_id from cwd
  when stored value is None or 'default' (read-side fallback for
  historical sessions persisted with incorrect project_id)
- ServerState.ensure_session() skips agent binding for child sessions
  to prevent deadlock on agent_lock held by parent stream and avoid
  overwriting parent's session_id on the shared agent
- BaseAgent suppresses ValueError on ContextVar.reset() during
  GeneratorExit cleanup in different async contexts
- Added tests for project_id inheritance and child session binding skip
Child subagent sessions emit StepFinishPart events that bubble through
process_stream(). Previously, any StepFinishPart (including children's)
would set _step_finish_emitted=True. If the parent stream was
interrupted after a child completed but before the parent's
StreamCompleteEvent, finalize() would skip emitting the parent's
StepFinishPart — leaving the parent session stuck in 'working' state
in the TUI.

Now only StepFinishPart events whose session_id matches the adapter's
session_id trigger _step_finish_emitted, so child StepFinishParts
cannot suppress the parent's fallback.
… UnboundLocalError

When CancelledError fires before the agent_lock is acquired, the except
handler at line ~518 references the  local variable which was only
defined inside . This causes UnboundLocalError
crash in the error handler itself.

Fix: Move  before the
block so the except handler always has a valid reference.

Test: test_question_abort_regression.py::TestUnboundLocalErrorInExceptHandler
…essage processing

When question_for_user raises RunAbortedError (user cancels questionnaire),
process_stream caught it via except Exception and yielded SessionErrorEvent,
then the iterator completed normally. This meant _process_message_locked
followed the normal completion path which:

1. Did NOT set MessageAbortedError on the assistant message
2. Did NOT add the aborted assistant message to agent.conversation.chat_messages

This corrupted the agent conversation history - on the next message,
the LLM saw a partial tool call (question_for_user called but no result),
causing unpredictable behavior and eventually the TUI black screen.

Fix:
- process_stream: Re-raise RunAbortedError (like CancelledError) instead of
  swallowing it, so the caller can properly handle it
- _process_message_locked: Add RunAbortedError to the except clause alongside
  CancelledError and TimeoutError, with proper reason string

Tests: test_question_abort_regression.py (all 5 RunAbortedError tests now pass)
…revent agent_lock deadlock

When the agent awaits a question answer (Future.await) while holding
agent_lock, and the TUI disconnects (SSE connection drops), the Future
is never resolved and agent_lock is never released. On reconnect, all
requests needing agent_lock deadlock permanently.

Fix:
- Add ServerState.cancel_all_pending_questions() which cancels all
  pending question Futures and returns their IDs
- Call it from the SSE disconnect handler in global_routes.py _event_generator
- When a question Future is cancelled, CancelledError propagates through
  input_provider -> question_for_user -> RunAbortedError ->
  process_stream (re-raises) -> _process_message_locked (handles like
  CancelledError) -> agent_lock released, session marked idle

Tests:
- test_cancel_all_pending_questions_cancels_futures: verifies Future
  cancellation releases agent_lock
- test_cancel_all_pending_questions_allows_new_message_after_sse_disconnect:
  verifies new session message succeeds after disconnect
…de_server test suite

Root causes and fixes:

1. test_cancelled_message.py (6 failures):
   - MessageRequest.variant does not exist (variant is on ModelRef, not MessageRequest)
   - UserMessage also has no variant field
   - Fix: Remove variant=request.variant from _create_user_message helper

2. test_command_execution.py (6 failures):
   - pool.skill_commands was a Mock (truthy but not iterable)
   - session_routes.py execute_command checks 'request.command in state.pool.skill_commands'
   - Fix: Set pool.skill_commands = None in conftest mock_pool

3. test_session_cwd_consistency.py (2 failures):
   - Tests expected create_session to use env.cwd for session.directory
   - But state.base_path explicitly uses working_dir for routing, not env.cwd
   - env.cwd is for agent tool isolation, routing anchors to working_dir
   - Fix: Update tests to expect working_dir-based directory

4. test_session_title_fixes.py + test_session_title_integration.py (7 failures):
   - pool.sessions.store was AsyncMock (saves absorbed, load_session returns None)
   - _generate_title_from_prompt did not persist title when _generate_title_core was mocked
   - agent.list_sessions was AsyncMock(return_value=[]) (created sessions invisible)
   - Fix: Wire pool.sessions.store to storage_manager, add update_session_title
     call in _generate_title_from_prompt, delegate agent.list_sessions to storage

Result: 515 passed, 0 failed, 9 xpassed (was 494 passed, 27 failed)
…returns None

Root cause: When the TUI creates multiple sessions and then sends messages
to each, POST /session/{id}/message returns 404 for all but the last
session. This happens because:

1. create_session saves to pool.sessions.store (MemorySessionStore)
2. agent.load_session reads from pool.storage (StorageManager/SQL)
3. These are DISCONNECTED storage backends
4. get_or_load_session only returned cached sessions when
   agent.session_id matched, falling through to agent.load_session
   for all others — which returns None → 404

Fix in get_or_load_session:
- Return cached sessions from state.sessions even when agent.session_id
  doesn't match and agent.load_session returns None
- Try to reload history from storage, but gracefully handle the case
  where the session hasn't been persisted to StorageManager yet

Fix in create_session:
- Also save to pool.storage (StorageManager) so agent.load_session
  can find the session, not just pool.sessions.store (MemorySessionStore)

Red flag test: test_session_cache_404.py (5 tests) reproduces the exact
TUI pattern: create 4 sessions, send message to each → was 404, now 200

520 passed, 0 failed (was 515 passed)
…s sessions immediately

The CLI TUI's sync.tsx handles session.updated (upsert) but NOT
session.created (insert-only).  Without a session.updated SSE event,
the TUI relies solely on the async REST session.sync() call to populate
its SolidJS store, causing a black screen while the store is empty.

Fixes:
- ensure_session() early return now broadcasts SessionUpdatedEvent
  (previously silently returned without any event)
- get_or_load_session() cold-load path broadcasts SessionUpdatedEvent
  (previously loaded from storage with no SSE notification)
- create_session() now broadcasts SessionUpdatedEvent alongside
  SessionCreatedEvent (matching official OpenCode server behavior)
- fork_session() now broadcasts SessionUpdatedEvent alongside
  SessionCreatedEvent

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add Cache-Control, X-Accel-Buffering, and X-Content-Type-Options
headers to /global/event and /event SSE endpoints, matching the
official OpenCode server behavior. Without these, reverse proxies
(nginx, Cloudflare) may buffer SSE events causing delayed delivery.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… SQLModelProvider

- SQLModelProvider session methods now use self.engine directly instead of
  creating one-shot SQLSessionStore instances that disposed the shared engine
- SQLSessionStore.__aexit__ no longer disposes shared engine from get_shared_engine()
- Add load_sessions_batch() for batch session loading

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Replace N+1 load_session calls with single load_sessions_batch query
- Add StorageManager.load_sessions_batch() delegating to provider
- Fix dead 'return result' after refactoring

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
_generate_title_from_prompt() was skipping generation because
'if existing:' was truthy for the default 'New Session' title.
Now checks 'if existing and existing != "New Session"' so
generation proceeds when only the placeholder is set.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…_model

_generate_title_core() was passing title_generation_model directly to
infer_model(), which fails for variant names like 'ack-dev' with
'Unknown model' error. Now checks _model_variants first (populated by
AgentPool from manifest.model_variants) and only falls back to
infer_model for non-variant model strings.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Conversation.title is now the single source of truth. _session_from_db
and _from_db_model always sync row.title into metadata['title'] instead
of skipping when a stale entry exists. This fixes the bug where
update_session_title only wrote the column but load_session returned
the stale 'New Session' from metadata_json.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
When SpawnSessionStart has no description but metadata.prompt contains the actual task, the child session first user message now correctly shows the prompt content instead of a generic 'Task: {source_name}' fallback.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…e text parts

The TUI's UserMessage component renders text from store.part[message.id].
Without PartUpdatedEvent, the store has the message but no parts,
so the first user message card in a subagent session appears empty.
Add PartUpdatedEvent.create() yields after add_text_part() in both
_process_spawn_start and _process_subagent_event fallback paths.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Parent agent holds agent_lock during entire streaming. When TUI
navigates to a child session, REST endpoints call get_or_load_session
which acquires agent_lock, blocking until the parent finishes.

Add fast-path for child sessions (parent_id is set) that returns
data directly from in-memory state without acquiring agent_lock.
This is safe because child sessions never rebind the shared agent.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… for TUI upsert

The CLI TUI's sync.tsx handles session.updated (upsert into store)
but does not handle session.created (insert-only). Without also
broadcasting SessionUpdatedEvent, child sessions created by
ensure_session are invisible to the TUI until a REST API sync.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
OpenCode's workspace feature is entirely optional and behind an
experimental flag. AgentPool not sending workspace fields is fully
backward-compatible with all OpenCode versions. Remove workspace
code to simplify the opencode server.

Removed:
- WorkspaceInfo, WorkspaceConnectionStatus, WorkspaceConnectionState models
- workspace field from GlobalEvent
- workspace_id field from Session
- /experimental/workspace and /experimental/workspace/status endpoints
- Workspace routing rule (3) from tui_event_filter (4 rules → 3 rules)
- workspace param from GlobalEventFactory and routing-check endpoint
- workspace_id from session creation and metadata persistence
- All workspace-related tests (test_workspace_routes.py deleted)

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements OpenCode 1.4.4+ compatibility, featuring OTLP telemetry sinks, global management routes, and a revised SSE protocol using GlobalEvent envelopes. It resolves session synchronization issues by ensuring child sessions inherit project metadata and introduces per-session async prompt queuing to prevent UI blocking. The storage layer is optimized with batch session loading and project persistence. Feedback highlights the need to refactor duplicated fast-path logic for child sessions into a shared helper, refine session persistence by narrowing exception handling, and replace the non-atomic 'delete-then-insert' upsert pattern in the SQL provider with ON CONFLICT statements to ensure data integrity under concurrency.

Comment thread src/agentpool_server/opencode_server/routes/message_routes.py
Comment thread src/agentpool_server/opencode_server/routes/session_routes.py
Comment thread src/agentpool_storage/sql_provider/sql_provider.py
@Leoyzen Leoyzen changed the title Feature/yuchen.liu/fix opencode version feat(opencode-server): OpenCode v1.4.4+ compatibility — GlobalEvent SSE, async prompt queue, session sync fixes Apr 19, 2026
@Leoyzen
Leoyzen merged commit 7683dfd into develop/agentic Apr 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant