Activity signals (tool calls like compress, grep, bash) were treated as
'progress' by the stagnation detector, resetting the stagnation counter
every cycle. This prevented MAX_STAGNATION_COUNT from being reached,
causing infinite continuation loops when models degrade to minimal
responses in long sessions (e.g. GLM-5.1 at ~100K tokens).
Stagnation now only tracks actual todo state changes: incomplete count
decrease, completed count increase, or todo snapshot change. Tool-level
activity no longer resets the stagnation counter.
Both injectors call resolveRegisteredAgentName, which returns the
registered alias verbatim. OpenCode TUI registers agent names with
leading zero-width characters (U+200B) for sort ordering, so that
alias can be e.g. "\u200B\u200BAtlas - Plan Executor". Passing it
directly to promptAsync produces "Agent not found" because the
OpenCode SDK does an exact match against its canonical display
name registry.
Strip the ZWSP sort prefix on the resolved name before sending it
to promptAsync in:
- src/hooks/atlas/boulder-continuation-injector.ts
- src/hooks/todo-continuation-enforcer/continuation-injection.ts
Add regression tests asserting promptAsync receives the canonical
display name (no \u200B) even when the registered alias carries
a ZWSP sort prefix. Same root cause class as #3494 / #3547. Tests
were RED on dev before the fix and GREEN after.
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.
When using `opencode run`, the process exits prematurely if the main
agent dispatches background subtasks. The CLI completion checker relies
on `session.children()` + `session.status()` APIs which cannot see
tasks in "pending" state (no session created yet) or tasks whose
sessions are momentarily idle between operations.
This fix bridges BackgroundManager state to the CLI completion checker
using the existing run-continuation-state marker system:
- Add "background-task" continuation marker source
- BackgroundManager writes/clears markers on task lifecycle events
(launch, cancel, complete, crash)
- CLI completion checker blocks exit when marker is active
- Fix todo-continuation-enforcer to also check "pending" task status
Closes#3452
handler.ts now catches ContextLengthError/prompt-too-long errors and
stops continuation instead of retrying with an even larger context.
91 tests pass, tsc clean.
Closes#2462
All 5 continuation paths now send variant as top-level body field:
- boulder-continuation-injector.ts
- ralph-loop/continuation-prompt-injector.ts
- todo-continuation-enforcer/continuation-injection.ts
- unstable-agent-babysitter-hook.ts
- session-recovery/resume.ts
Plus type/helper updates in atlas, todo-continuation-enforcer,
unstable-agent-babysitter, and session-recovery.
TDD: 18 regression tests added, all pass. tsc clean.
Display names with parentheses like 'Atlas (Plan Executor)' cause HTTP
header validation errors in x-opencode-agent-name. This was blocking
Atlas/Prometheus from working via /start-work and auto-retry.
Changes:
- Display names: parens -> dashes ('Atlas - Plan Executor')
- Hooks (start-work, no-hephaestus-non-gpt, no-sisyphus-gpt): use
config keys ('atlas', 'sisyphus', 'hephaestus') for agent API fields
- auto-retry: use config key instead of display name for promptAsync
- agent-override-protection: handle dash-suffix normalization
- Updated all test expectations to match new format
Closes#3138
The todo-continuation-enforcer was passing raw agent names (which could be
display names like 'Sisyphus (Ultraworker)') to promptAsync. These names
contain spaces/parentheses that violate HTTP header specs, causing the
x-opencode-agent-name header validation to fail with 'unknown error' toast.
Added normalizeAgentForPromptKey() that converts display names to config keys
(e.g., 'Sisyphus (Ultraworker)' -> 'sisyphus') before API calls.
TDD: Added regression test that verifies config key is sent to promptAsync.
When a user cancels a generation (ESC x2), all three idle hooks could
enter a permanently broken state:
1. **todo-continuation-enforcer**: consecutiveFailures accumulated from
abort-caused promptAsync failures, eventually hitting MAX_CONSECUTIVE_FAILURES
and permanently stopping continuation injection.
2. **unstable-agent-babysitter**: no abort awareness at all — would keep
firing reminders after user cancelled the session.
3. **runtime-fallback**: retry dedupe keys and pending fallback state
persisted across cancellation, blocking legitimate error recovery.
Fix:
- Add shared `isAbortError()` utility for consistent abort detection
- Reset consecutiveFailures and clear stale state on AbortError in all hooks
- Track `lastCancelledAt` in todo-continuation-enforcer for abort window
- Add abort-awareness to unstable-agent-babysitter (skip if recently cancelled)
- Clear runtime-fallback retry state on abort errors
Tests: 61 pass, 0 fail across all 3 affected hook test suites.
Closes#2984