Prevent ZWSP sort prefixes from leaking into stored agent names, config
key lookups, and override-protection normalization. Ensures prefixed
list-display names resolve correctly throughout the pipeline.
🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) assistance
Populate AGENT_LIST_SORT_PREFIXES for sisyphus/hephaestus/prometheus/atlas
so the TUI agent list renders in canonical order. Update dependent tests
to use getAgentListDisplayName() instead of hardcoded display strings.
🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) assistance
- provider.test.ts: reset discovery cache before/after refresh tests to prevent
cross-test contamination via module-level discoveryCache singleton
- completion-promise-session-negative.test.ts: update negative test case to use
non-oracle agent (hephaestus) since oracle tool_result VERIFIED detection was
intentionally added in 56f2a9df
Bug fixes:
1. OAuth token refresh (#3149): buildHttpRequestInit() now attempts silent refresh
via refresh_token before triggering full browser re-auth. Added refresh() method
to McpOAuthProvider. Includes test isolation fix for discovery mock.
2. Quota error STOP (#3126): Added STOP_MESSAGE_PATTERNS in model-error-classifier
that take precedence over RETRYABLE_MESSAGE_PATTERNS. Message-only quota errors
now non-retryable. Runtime-fallback: quota_exceeded with 'retrying in' signal
still triggers fallback (provider-managed auto-retry). Restored removed patterns.
3. Compaction loop (#3127): MAX_RECOVERY_ATTEMPTS=3 cap + additional suppression
guard from opencode session in degradation monitor.
Also: refactored extractAutoRetrySignal to auto-retry-signal.ts, new regression
tests for quota classifier and compaction degradation monitor.
Extract hephaestus into agentConfig before prometheus so the config
handler naturally assembles agents in the canonical order instead of
relying solely on reorderAgentsByPriority to fix the wrong intermediate
order. Also fix test data that had atlas/prometheus swapped and add
missing hephaestus to agent-config-integration test lists.
Tool return values contained CTAs ("Use background_output to check") that
caused models to immediately poll instead of waiting for <system-reminder>
notifications. Unified all 5 tool return messages with explicit "Do NOT call
background_output now" guard, added pre-notification warning to BackgroundOutput
tool description, and strengthened Background Result Collection sections across
all 3 Sisyphus prompt variants (default, gpt-5-4, main) with BLOCKING
anti-pattern enforcement.
🤖 Generated with assistance of [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
Add MAX_RECOVERY_ATTEMPTS (3) guard to post-compaction degradation recovery.
On models like Kimi/Minimax that repeatedly generate no-text responses,
the recovery loop would trigger indefinitely as each compaction reset the
recovery counter via onSessionCompacted.
The recovery count now persists across compaction cycles per session and
is only reset on session deletion (clear()), not on every compaction.
- Fix zod/v4 imports in background-task schema tests
- Remove ZWSP prefix from agent-key-remapper test (fixed in #3136)
- Use toMatchObject for openai-only catalog tests (fallback_models added by #3144)
- Replace z.toJSONSchema (zod v4) with zodToJsonSchema (zod v3 compat)
- Fix task-list.ts type narrowing for zod v3 inferred types
zod v3 infers .default([]) fields as string[] | undefined in z.infer
output type (unlike v4 which marks them as string[]). Add nullish
coalescing guards and use any[] for the readJsonSafe result array
to avoid the type mismatch in task-list and task-update.
The test overrides globalThis.setTimeout which conflicts with other atlas
tests when batched by bun test. Adding mock.module forces run-ci-tests.ts
to run this file in its own isolated process.
Also added setSessionAgent for descendant session to fix null agent lookup
in non-SQLite CI environments.
Tests relied on leaked agent registration from other tests when run locally.
In CI, each test batch runs in isolation so the session agent was null,
causing getLastAgentFromSession to return null and the test to incorrectly
pass (return true instead of false).
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.
- Add lineage-aware continuation injection logic
- Support for tracking multiple session types (direct vs appended)
- Update tests for new lineage continuation scenarios
- Add session origin validation in continuation flow
🤖 Generated with assistance of OhMyOpenCode
- Update continuation-state to use session_origins from boulder state
- Add isTrackedDescendantSession helper for lineage detection
- Integrate getLastAgentFromSession for reliable agent detection
- Update completion tests for new lineage-aware continuation logic
- Add JSON backend tests for continuation state
🤖 Generated with assistance of OhMyOpenCode
- Add session_origins field to BoulderState type
- Track direct vs appended session origins in storage layer
- Add migration logic for existing state files
- Add comprehensive tests for session origin tracking
🤖 Generated with assistance of OhMyOpenCode
- Add lineage-aware continuation injection logic
- Support for tracking multiple session types (direct vs appended)
- Update tests for new lineage continuation scenarios
- Add session origin validation in continuation flow
🤖 Generated with assistance of OhMyOpenCode
- Update recent-model-resolver for session origin awareness
- Update resolve-active-boulder-session with lineage support
- Add comprehensive test coverage for boulder session resolution
- Add fallback tests for recent model resolver
- Update index tests for new lineage tracking
🤖 Generated with assistance of OhMyOpenCode
- Update continuation-state to use session_origins from boulder state
- Add isTrackedDescendantSession helper for lineage detection
- Integrate getLastAgentFromSession for reliable agent detection
- Update completion tests for new lineage-aware continuation logic
- Add JSON backend tests for continuation state
🤖 Generated with assistance of OhMyOpenCode
- Update background-launch-session-tracking to track session origins
- Add tests for lineage-aware retry scheduling
- Update tool-execute-after to support new tracking
- Add comprehensive tests for background launch continuation
🤖 Generated with assistance of OhMyOpenCode
- Sort messages by creation timestamp for accurate last agent detection
- Add fallback to filename sorting for deterministic ordering
- Add JSON backend test coverage
- Update SQLite backend tests for timestamp-aware sorting
🤖 Generated with assistance of OhMyOpenCode
- Add session_origins field to BoulderState type
- Track direct vs appended session origins in storage layer
- Add migration logic for existing state files
- Add comprehensive tests for session origin tracking
🤖 Generated with assistance of OhMyOpenCode
OpenCode's fromPlugin wrapper skips Zod validation for plugin tools, so
LLMs can omit required args like description without getting an error.
When Atlas orchestrates and the model omits description, it flows through
as undefined to manager.launch() and background task notifications show
'undefined' for all completed tasks.
Two fixes:
- Add runtime validation for description in delegate-task tool (matches
existing run_in_background and load_skills validation pattern)
- Defensive fallback in notification template: use task ID when
description is missing instead of rendering 'undefined'
Update continuation injector to work with new lineage detection.
Refine idle event handling for proper subagent session tracking
and agent mismatch detection.
🤖 Generated with assistance of OhMyOpenCode
Make getContinuationState async to support client-side lineage checks.
Add hasActiveBoulderContinuation with agent eligibility verification
and subagent session tracking for proper boulder session inheritance.
🤖 Generated with assistance of OhMyOpenCode
Add flag to track when continuation is being injected to prevent
race conditions in boulder session management.
🤖 Generated with assistance of OhMyOpenCode
- Remove kimi from auto-detected unstable agents in category-resolver (only gemini/minimax remain)
- Add resolveCallID() to safely resolve callID/callId/call_id variants from tool context
- Use resolveCallID across all 5 delegate task execution paths (sync, background, unstable, continuations)
- Update writing category test to verify kimi runs sync when kimi provider is available
- Add atlas metadata preservation tests confirming tool-execute-after does not clobber metadata
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
The \u200B (zero-width space) in AGENT_LIST_SORT_PREFIXES leaked into
the x-opencode-agent-name HTTP header, causing invalid header errors.
Agent ordering is handled by agent-priority-order.ts via numeric order
fields and insertion order — the ZWSP prefix was redundant.
The (message.includes("agent") && message.includes("undefined"))
pattern could match unrelated errors like "The agent returned undefined
for the configuration", triggering a false fallback that hides the real
failure.
The two precise patterns are sufficient:
- "Agent not found" — canonical SDK validation error
- "agent.name" — property access error on undefined agent config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: loadPluginConfig() unconditionally switched userConfigPath to the
canonical name after calling migrateLegacyConfigFile(), even when migration
failed (e.g. file lock on Windows, permission denied). This left the config
path pointing to a non-existent file, so the plugin config silently loaded
as empty defaults.
Additionally, several fallback/default paths were hardcoded to the legacy
'oh-my-opencode' basename instead of using CONFIG_BASENAME ('oh-my-openagent'),
causing CLI config commands (writeOmoConfig, detectCurrentConfig) to write to
the wrong filename.
Changes:
- plugin-config.ts: check migrateLegacyConfigFile() return value; only switch
to canonical path if migration succeeded OR the canonical file already exists
- opencode-config-dir.ts: use CONFIG_BASENAME for omoConfig path in
getOpenCodeConfigPaths()
- config-context.ts: getOmoConfigPath() now uses detectPluginConfigFile() to
find whichever name variant actually exists on disk
- plugin-config.ts: default fallback paths use CONFIG_BASENAME instead of
hardcoded legacy name
- Added test: loadPluginConfig still loads config when migration fails
(read-only directory simulation)
1. isAgentNotFoundError now handles:
- Plain objects with .message field (not just Error instances)
- "agent.name"/"undefined" error variants from SDK validation
- The original "Agent not found" format
2. New tests:
- agent.name/undefined error variant triggers fallback
- Plain object errors with .message field trigger fallback
- "fallback also fails" test now verifies retry was attempted (callCount=2)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. [HIGH] Tool restrictions recomputed for fallback agent via
buildFallbackBody() — no longer inherits original agent's restrictions.
2. [HIGH] Double-retry race prevented — handleSessionErrorEvent now
returns early for agent-not-found errors, since the prompt catch
block already handles them with agent fallback. This prevents
tryFallbackRetry from racing with a model-level retry on the same
error (the "not found" pattern in RETRYABLE_MESSAGE_PATTERNS).
3. [MEDIUM] task.agent updated to FALLBACK_AGENT after successful
fallback — notifications, toast, and logging reflect actual agent.
4. [MEDIUM] FALLBACK_AGENT exported from spawner.ts and imported into
manager.ts — single source of truth.
5. [LOW] resumeTask fallback now uses promptWithModelSuggestionRetry
(consistent with startTask), getting timeout protection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>