* fix: resolve deadlock in config handler during plugin initialization
The config handler and createBuiltinAgents were calling fetchAvailableModels
with client, which triggers client.provider.list() API call to OpenCode server.
This caused a deadlock because:
- Plugin initialization waits for server response
- Server waits for plugin init to complete before handling requests
Now using cache-only mode by passing undefined instead of client.
If cache is unavailable, the fallback chain will use the first model.
Fixes#1301
* test: add regression tests for deadlock prevention in fetchAvailableModels
Add tests to ensure fetchAvailableModels is called with undefined client
during plugin initialization. This prevents regression on issue #1301.
- config-handler.test.ts: verify config handler does not pass client
- utils.test.ts: verify createBuiltinAgents does not pass client
* test: restore spies in utils.test.ts to prevent test pollution
Add mockRestore() calls for all spies created in test cases to ensure proper cleanup between tests and prevent state leakage.
* test: restore fetchAvailableModels spy
---------
Co-authored-by: robin <robin@watcha.com>
Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
- Reduce prompt from 392 to 125 lines
- Add APPROVAL BIAS: approve by default, reject only for blockers
- Limit max 3 issues per rejection to prevent overwhelming feedback
- Remove 'ruthlessly critical' tone, add 'practical reviewer' approach
- Add explicit anti-patterns section for what NOT to reject
- Define 'good enough' criteria (80% clear = pass)
- Update tests to match simplified prompt structure
* fix: exclude prompt/permission from plan agent config
plan agent should only inherit model settings from prometheus,
not the prompt or permission. This ensures plan agent uses
OpenCode's default behavior while only overriding the model.
* test(todo-continuation-enforcer): use FakeTimers for 15x faster tests
- Add custom FakeTimers implementation (~100 lines)
- Replace all real setTimeout waits with fakeTimers.advanceBy()
- Test time: 104.6s → 7.01s
* test(callback-server): fix race conditions with Promise.all and Bun.fetch
- Use Bun.fetch.bind(Bun) to avoid globalThis.fetch mock interference
- Use Promise.all pattern for concurrent fetch/waitForCallback
- Add Bun.sleep(10) in afterEach for port release
* test(concurrency): replace placeholder assertions with getCount checks
Replace 6 meaningless expect(true).toBe(true) assertions with
actual getCount() verifications for test quality improvement
* refactor(config-handler): simplify planDemoteConfig creation
Remove unnecessary IIFE and destructuring, use direct spread instead
* test(executor): use FakeTimeouts for faster tests
- Add custom FakeTimeouts implementation
- Replace setTimeout waits with fakeTimeouts.advanceBy()
- Test time reduced from ~26s to ~6.8s
* test: fix gemini model mock for artistry unstable mode
* test: fix model list mock payload shape
* test: mock provider models for artistry category
---------
Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
- fetchAvailableModels now falls back to client.model.list() when cache is empty
- provider-models cache empty → models.json → client API (3-tier fallback)
- look-at tool explicitly passes registered agent's model to session.prompt
- Ensures multimodal-looker uses correctly resolved model (e.g., gemini-3-flash-preview)
- Add comprehensive tests for fuzzy matching and fallback scenarios
- Add kimiForCoding field to ProviderAvailability interface
- Add kimi-for-coding provider mapping in isProviderAvailable
- Include kimi-for-coding in Sisyphus fallback chain for non-max plan
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Remove uiSelectedModel from Atlas model resolution (use k2p5 as primary)
- Always overwrite provider-models.json on session start to prevent stale cache
- Add oracle vs artistry distinction in MANDATORY CERTAINTY PROTOCOL
- Update WHEN IN DOUBT examples with both delegation options
- Add artistry to IF YOU ENCOUNTER A BLOCKER section
- Add 'Hard problem (non-conventional)' row to AGENTS UTILIZATION table
- Update analyze-mode message with artistry specialist option
Oracle: conventional problems (architecture, debugging, complex logic)
Artistry: non-conventional problems (different approach needed)
- MUST search existing codebase for patterns before writing code
- MUST match project's existing conventions
- MUST write readable, human-friendly code
- Add variant: max to ultrabrain's gemini-3-pro fallback entry
- Rename STRATEGIC_CATEGORY_PROMPT_APPEND to ULTRABRAIN_CATEGORY_PROMPT_APPEND
- Keep original strategic advisor prompt content (no micromanagement instructions)
- Update description: use only for genuinely hard tasks, give clear goals only
- Update tests to match renamed constant
Subagents (explore, librarian, oracle, etc.) now use their own fallback
chain instead of inheriting the UI-selected model. This fixes the issue
where explore agent was incorrectly using Opus instead of Haiku.
- Add AgentMode type and static mode property to AgentFactory
- Each agent declares its own mode via factory.mode = MODE pattern
- createBuiltinAgents() checks source.mode before passing uiSelectedModel
- Fix sessionTag showing '[undefine]' when sessionID is undefined
- System events now display as '[system]' instead
- Fix message.updated expecting non-existent 'content' field
- SDK's EventMessageUpdated only contains info metadata, not content
- Content is streamed via message.part.updated events
- Add text preview to message.part.updated verbose logging
- Update MessageUpdatedProps type to match SDK structure
- Update tests to reflect actual SDK behavior
PR #1227 incorrectly removed resolved.model from the userModel chain,
assuming it was bypassing the fallback chain. However, resolved.model
contained the category's DEFAULT_CATEGORIES model (e.g., quick ->
claude-haiku-4-5), not the main session model.
Without resolved.model, when connectedProvidersCache is null and
availableModels is empty, category model resolution falls through to
systemDefaultModel (opus) instead of using the category's default.
This fix restores the original priority:
1. User category model override
2. Category default model (from resolved.model)
3. sisyphusJuniorModel
4. Fallback chain
5. System default
The run command's completion check had a race condition: when a session
transitions busy->idle before the LLM generates any output (empty
response or API delay), checkCompletionConditions() returns true because
0 incomplete todos + 0 busy children = complete. This caused the runner
to exit with 'All tasks completed' before any work was done.
Fix:
- Add hasReceivedMeaningfulWork flag to EventState
- Set flag on: assistant text content, tool execution, or message update
with actual content (all scoped to main session only)
- Guard completion check in runner poll loop: skip if no meaningful work
has been observed yet
This ensures the runner waits until the session has produced at least one
observable output before considering completion conditions.
Adds 6 new test cases covering the race condition scenarios.
* refactor(lsp): migrate to vscode-jsonrpc for improved stability
Replace custom JSON-RPC implementation with vscode-jsonrpc library.
Use MessageConnection with StreamMessageReader/Writer.
Implement Bun↔Node stream bridges for compatibility.
Preserve all existing functionality (warmup, cleanup, capabilities).
Net reduction of ~60 lines while improving protocol handling.
* fix(lsp): clear timeout on successful response to prevent unhandled rejections
---------
Co-authored-by: justsisyphus <justsisyphus@users.noreply.github.com>
* fix: expand override.category and explicit reasoningEffort priority (#1219)
Two bugs fixed:
1. createBuiltinAgents(): override.category was never expanded into concrete
config properties (model, variant, reasoningEffort, etc.). Added
applyCategoryOverride() helper and applied it in the standard agent loop,
Sisyphus path, and Atlas path.
2. Prometheus config-handler: reasoningEffort/textVerbosity/thinking from
direct override now use explicit priority chains (direct > category)
matching the existing variant pattern, instead of relying on spread
ordering.
Priority order (highest to lowest):
1. Direct override properties
2. Override category properties
3. Resolved variant from model fallback chain
4. Factory base defaults
Closes#1219
* fix: use undefined check for thinking to allow explicit false
- BackgroundManager.shutdown() now aborts all running child sessions via
client.session.abort() before clearing state, preventing orphaned
opencode processes when parent exits
- Add onShutdown callback to BackgroundManager constructor, used to
trigger TmuxSessionManager.cleanup() on process exit signals
- Interactive bash session hook now aborts tracked subagent opencode
sessions when killing tmux sessions (defense-in-depth)
- Add 4 tests verifying shutdown abort behavior and callback invocation
Closes#1240
Tests in utils.test.ts were written before bffa1ad introduced
connected-providers-cache fallback in resolveModelWithFallback.
Update assertions to match the new resolution path:
- Oracle resolves to openai/gpt-5.2 via cache (not systemDefault)
- Agents are created via cache fallback even without systemDefaultModel
When delegate_task uses subagent_type, extract the matched agent's model
object and pass it explicitly to session.prompt/manager.launch. This
ensures the model is always in the correct object format regardless of
how OpenCode handles string→object conversion for plugin-registered
agents.
Closes#1225
* fix(start-work): prevent overwriting session agent if already set; inherit parent model for subagent types
* fix(model): include variant in StoredMessage model structure for better context propagation
* fix(injector): include variant in model structure for hook message injection
- Add uiSelectedModel parameter to resolveModelWithFallback()
- Update model resolution priority: UI Selection → Config Override → Fallback → System Default
- Pass config.model as uiSelectedModel in createBuiltinAgents()
- Fix ProviderModelNotFoundError when model is unset in config but selected in UI
- Remove resolved.model from userModel in tools.ts (was bypassing fallback chain)
- Use connected providers cache in model-resolver when availableModels is empty
- Allows proper provider selection (e.g., github-copilot instead of google)
When model cache is empty, the fallback chain resolution was blindly
trusting connected providers without verifying if the model actually
exists. This caused errors when a provider (e.g., opencode) was marked
as connected but didn't have the requested model (e.g., claude-haiku-4-5).
Now skips fallback chain entirely when model cache is unavailable and
falls through to system default, letting OpenCode handle the resolution.
When multimodal-looker agent returns empty/malformed response, the SDK
throws 'JSON Parse error: Unexpected EOF'. This commit adds try-catch
around session.prompt() to provide user-friendly error message with
troubleshooting guidance.
- Add error handling for JSON parse errors with detailed guidance
- Add error handling for generic prompt failures
- Add test cases for both error scenarios
- Change all prometheus references to plan agent in ultrawork mode
- Add MANDATORY OUTPUT section to ULTRAWORK_PLANNER_SECTION:
- Parallel Execution Waves structure
- Dependency Matrix format
- TODO List with category + skills + parallel group
- Agent Dispatch Summary table
- Plan agent now outputs parallel task graphs for orchestrator execution