Prevent cross-file mock.module leakage by restoring Bun mocks after recovery-hook test, so executor tests always run against the real module implementation.
Stop patching global timers in every lock-management test. Use scoped fake timers only in continuation tests so lock/notification assertions remain deterministic in CI.
Split 1021-line index.ts into 10 focused modules per project conventions.
New structure:
- error-classifier.ts: error analysis with dynamic status code extraction
- agent-resolver.ts: agent detection utilities
- fallback-state.ts: state management and cooldown logic
- fallback-models.ts: model resolution from config
- auto-retry.ts: retry helpers with mutual recursion support
- event-handler.ts: session lifecycle events
- message-update-handler.ts: message.updated event handling
- chat-message-handler.ts: chat message interception
- hook.ts: main factory with proper cleanup
- types.ts: updated with HookDeps interface
- index.ts: 2-line barrel re-export
Embedded fixes:
- Fix setInterval leak with .unref()
- Replace require() with ESM import
- Add log warning on invalid model format
- Update sessionLastAccess on normal traffic
- Make extractStatusCode dynamic from config
- Remove unused SessionErrorInfo type
All 61 tests pass without modification.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Make provider auto-retry signal detection respect timeout_seconds setting:
- When timeout_seconds=0, disable quota-based fallback escalation
- Only treat auto-retry signals as errors when timeout is enabled
- Add test to verify behavior when timeout_seconds is disabled
- Update documentation to explain timeout_seconds=0 behavior
This allows users to disable timeout-based fallbacks while keeping
error-based fallback functionality intact.
Refactor retry signal detection to be provider-agnostic:
- Replace hardcoded Copilot/OpenAI checks with generic pattern matching
- Detect any provider message containing limit/quota keywords + [retrying in X]
- Add OpenAI pattern: 'usage limit has been reached [retrying in X]'
- Update logging to use generic 'provider' instead of specific names
- Add 'usage limit has been reached' to RETRYABLE_ERROR_PATTERNS
This enables fallback escalation for any provider that signals automatic
retries due to quota/rate limits, not just Copilot and OpenAI.
Closes PR discussion: generalize retry pattern detection
- Extract duplicated auto-retry logic (~40 lines each) from session.error and
message.updated handlers into shared autoRetryWithFallback() helper
- Fix userFallbackModels path in model-resolution-pipeline to respect
constraints.connectedProviders parameter instead of reading cache directly,
matching the behavior of categoryDefaultModel and fallbackChain paths
Bug fixes:
1. extractStatusCode: handle nested data.statusCode (Anthropic error structure)
2. Error regex: relax credit.*balance.*too.*low pattern for multi-char gaps
3. Zod schema: bump max_fallback_attempts from 10 to 20 (config rejected silently)
4. getFallbackModelsForSession: fallback to sisyphus/any agent when session.error lacks agent
5. Model detection: derive model from agent config when session.error lacks model info
6. Auto-retry: resend last user message with fallback model via promptAsync
7. Persistent fallback: override model on every chat.message (not just pendingFallbackModel)
8. Manual model change: detect UI model changes and reset fallback state
9. Agent preservation: include agent in promptAsync body to prevent defaulting to sisyphus
Additional:
- Add sessionRetryInFlight guard to prevent double-retries
- Add resolveAgentForSession with 3-tier resolution (event → session memory → session ID)
- Add normalizeAgentName for display names like "Prometheus (Planner)" → "prometheus"
- Add resolveAgentForSessionFromContext to fetch agent from session messages
- Move AGENT_NAMES and agentPattern to module scope for reuse
- Register runtime-fallback hooks in event.ts and chat-message.ts
- Remove diagnostic debug logging from isRetryableError
- Add 400 to default retry_on_errors and credit/balance patterns to RETRYABLE_ERROR_PATTERNS
The \b word boundary regex treats '-' as a boundary, causing
'sisyphus-junior-session-123' to incorrectly match 'sisyphus'
instead of 'sisyphus-junior'.
Sorting agent names by length (descending) ensures longer names
are matched first, fixing the hyphenated agent detection issue.
Fixes cubic-dev-ai review issue #8
- Add normalizeFallbackModels helper to centralize string/array normalization (P3)
- Export RuntimeFallbackConfig and FallbackModels types from config/index.ts
- Fix agent detection regex to use word boundaries for sessionID matching
- Improve tests to verify actual fallback switching logic (not just log paths)
- Add SessionCategoryRegistry cleanup in executeSyncTask on completion/error (P2)
- All 24 runtime-fallback tests pass, 115 delegate-task tests pass
Replace word-boundary regex with stricter patterns that match
status codes only at start/end of string or surrounded by whitespace.
Prevents false matches like '1429' or '4290'.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Implement full fallback_models support across all integration points:
1. Model Resolution Pipeline (src/shared/model-resolution-pipeline.ts)
- Add userFallbackModels to ModelResolutionRequest
- Process user fallback_models before hardcoded fallback chain
- Support both connected provider and availability checking modes
2. Agent Utils (src/agents/utils.ts)
- Update applyModelResolution to accept userFallbackModels
- Inject fallback_models for all builtin agents (sisyphus, oracle, etc.)
- Support both single string and array formats
3. Model Resolver (src/shared/model-resolver.ts)
- Add userFallbackModels to ExtendedModelResolutionInput type
- Pass through to resolveModelPipeline
4. Delegate Task Executor (src/tools/delegate-task/executor.ts)
- Extract category fallback_models configuration
- Pass to model resolution pipeline
- Register session category for runtime-fallback hook
5. Session Category Registry (src/shared/session-category-registry.ts)
- New module: maps sessionID -> category
- Used by runtime-fallback to lookup category fallback_models
- Auto-cleanup support
6. Runtime Fallback Hook (src/hooks/runtime-fallback/index.ts)
- Check SessionCategoryRegistry first for category fallback_models
- Fallback to agent-level configuration
- Import and use SessionCategoryRegistry
Test Results:
- runtime-fallback: 24/24 tests passing
- model-resolver: 46/46 tests passing
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Add Category-level fallback_models support in getFallbackModelsForSession()
- Try agent-level fallback_models first
- Then try agent's category fallback_models
- Support all builtin agents including hephaestus, sisyphus-junior, build, plan
- Expand agent name recognition regex to include:
- hephaestus, sisyphus-junior, build, plan, multimodal-looker
- Add comprehensive test coverage (6 new tests, total 24):
- Model switching via chat.message hook
- Agent-level fallback_models configuration
- SessionID agent pattern detection
- Cooldown mechanism validation
- Max attempts limit enforcement
All 24 tests passing
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add configuration schemas for runtime model fallback feature:
- RuntimeFallbackConfigSchema with enabled, retry_on_errors,
max_fallback_attempts, cooldown_seconds, notify_on_fallback
- FallbackModelsSchema for init-time fallback model selection
- Add fallback_models to AgentOverrideConfigSchema and CategoryConfigSchema
- Export types and schemas from config/index.ts
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- keyword-detector: always set variant to 'max' when ultrawork/ulw keyword detected
- chat-message: remove variant resolution logic to passthrough TUI variant unchanged
- Tests updated to reflect new behavior
🤖 Generated with OhMyOpenCode assistance
Breaking Changes:
- Change hashline format from 'lineNum:hex|content' to 'lineNum#CID:content'
- Replace hex-based hashing (00-ff) with CID-based hashing (ZPMQVRWSNKTXJBYH nibbles)
- Simplify constants: HASH_DICT → NIBBLE_STR + HASHLINE_DICT
- Update patterns: HASHLINE_PATTERN → HASHLINE_REF_PATTERN + HASHLINE_OUTPUT_PATTERN
Benefits:
- More compact and memorable CID identifiers
- Better alignment with LSP line reference format (lineNum#ID)
- Improved error messages and diff metadata clarity
- Remove unused toHashlineContent from diff-enhancer hook
Updates:
- Refactor hash-computation for CID generation
- Update all diff-utils to use new format
- Update hook to use raw content instead of hashline format
- Update tests to match new expectations
🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Apply the internal initiator marker to automated continuation, recovery, babysitter, stop-hook, and hook-message injections so Copilot attribution consistently sets x-initiator=agent for system-generated prompts.
Align edit/write hashline handling with TUI expectations by preserving metadata through tool execution, keeping unified diff raw to avoid duplicated line numbers, and tightening read/write/edit outputs plus tests for reliable agent operation.
- Support write tool in addition to read tool
- Fix early termination when encountering non-matching lines
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Generate unified diff for TUI display via metadata.diff
- Support write tool in addition to edit tool
- Hashline-format before/after content in filediff metadata
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add comprehensive test coverage for:
- Hash consistency validation between Read tool output and Edit tool validateLineRef
- Injected content isolation to prevent hashifying non-file-content lines
- Footer messages and system reminders that should pass through unchanged
Tests ensure Read hook properly handles content boundaries and maintains
hash validity for Edit tool operations.
🤖 Generated with assistance of oh-my-opencode
Capture file content before hashline edit execution and compute filediff
metadata after, enabling opencode TUI to render inline diffs for the
plugin's edit tool (which replaces the built-in EditTool).