When verification_pending is true and the agent has dispatched an Oracle
verification (verification_attempt_id is set), session.idle events that
arrive before tool-execute-after stores the Oracle session ID
(verification_session_id still undefined) caused handlePendingVerification
to fall through to handleFailedVerification. This injected a duplicate
'verification failed' continuation prompt, spawning a second Oracle.
The fix adds a guard in handlePendingVerification: when
verification_attempt_id is set but verification_session_id is not, Oracle
dispatch is in flight and the handler returns early instead of declaring
failure. The pending wake will retry on the next session.idle.
Regression test added in given/when/then style proving the race sequence:
1. ULW loop detects DONE, enters verification_pending
2. Oracle dispatch stamps verification_attempt_id (tool-execute-before)
3. Second session.idle fires before tool-execute-after stores session ID
4. Handler must NOT call handleFailedVerification
RED (before fix): 2 prompt injections (duplicate Oracle)
GREEN (after fix): 1 prompt injection (correct)
Fixes#4256Fixes#4019
When a user explicitly configures a model for the multimodal-looker
agent (e.g. zhipuai-coding-plan/glm-5.1), treat that model as
vision-capable even when its provider config does not declare
modalities.input or capabilities.input.image. This unblocks
vision-capable models that the provider config does not advertise.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The OpenCode TUI renders background subagent session entries using
`props.metadata.sessionId` as the navigation target. When the wait-loop
in delegate-task and background-task tools exits before the session is
assigned, but the session is created moments later (before metadata
publish), the published metadata had `sessionId: undefined`, leaving
the TUI entry stuck spinning with no clickable target.
Add a single late-fallback `manager.getTask(task.id)?.sessionId` check
between the wait-loop exit and metadata publish in both paths. This
closes the narrow race window that produced the symptom in #4252.
Regression test: `late-session-id-capture.test.ts` mocks the exact race
(launch returns no sessionId; getTask returns it after the wait loop).
Verified:
- npm run build: PASS
- npm test: exit code 0 (one unrelated pre-existing failure in
sisyphus-task > browserProvider propagation re: agent-browser skill)
Atlas was the only agent that bailed early (returning undefined) when
applyModelResolution returned undefined, even if the user had explicitly
configured agents.atlas.model. The general-agents.ts path already handled
this case by falling back to the user's override; sisyphus has its own
explicit-config check. Atlas had neither, so under edge cases (cold
provider cache, no system default, empty availableModels) Atlas was
silently dropped and OpenCode used its built-in default — surfaced as
the hardcoded claude-sonnet-4-6 in the reported bug.
This aligns atlas-agent.ts with the same defensive pattern used in
general-agents.ts: when resolution fails but the user has an explicit
model override, honor it directly instead of dropping the agent.
Adds a regression test covering the scenario where availableModels is
empty AND systemDefaultModel is undefined AND no provider cache exists.
Upstream OpenCode's Global.Path.bin resolves to ~/.cache/opencode/bin
(XDG cache), where its ripgrep auto-installer and many LSP servers land.
OMO's getOpenCodeBundledRg only checked the data-dir variant
(~/.local/share/opencode/bin), so when OpenCode had already downloaded
rg into its cache directory OMO would skip it and either redownload via
its own fallback installer or fall back to system grep.
Probe the cache-backed bin path first so OMO reuses tools OpenCode has
already installed.
Ship the documented Team Mode security-research capability as an .agents skill and expose /security-research through transition command wrappers.
The exact security-review slug has no reachable history hits; security-research is the documented missing artifact from README and issue #3887.
Refs #3887
Plan: plans/security-research-restore.md
In v4.1.0+, users observed duplicate assistant streams rendering the same
content in two languages simultaneously (e.g. Chinese + English), most often
at the end of a turn.
Root cause: ParentWakeNotifier.requeueWake() unconditionally requeued ANY
wake that arrived during the background-agent-parent-wake post-dispatch
hold window. When a duplicate completion edge fired during that hold, the
same wake was replayed after the hold expired, triggering a second prompt
dispatch and a parallel assistant stream.
The fix compares the new wake against dispatchedParentWakes.get(sessionID)
and drops identical wakes during the gate hold, while preserving the existing
requeue behavior for genuinely-new wakes and failed-dispatch retries.
Regression test added in given/when/then style covering the duplicate-during-
hold scenario (TDD red-then-green).
Fixes#4256Fixes#4019
Same Web-Response-on-Node hazard existed in ripgrep auto-download flow,
zip extraction helpers, and binary downloader streams. Switch to the new
Node-safe reader and ensure no spawn path escapes as unhandledRejection.
Related to #3919.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
OpenCode Desktop v1.14.41+ runs OMO inside a Node.js utility process.
The previous `new Response(proc.stdout).text()` call is Bun-/Web-API-specific
and crashed the Desktop sidecar on Windows when grep/glob were invoked.
Switch glob/grep cli to the new process-stream-reader + search-process-output
helpers. Behavior on Bun and CLI/Linux/macOS is unchanged. ripgrep auto-download,
PowerShell fallback, and rgSemaphore are preserved.
Fixes#3919
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Introduces:
- src/shared/process-stream-reader.ts: Buffer-concat stream reader compatible with both Bun and Node ChildProcess stdout (replaces Web Response API usage)
- src/tools/shared/search-process-output.ts: structured subprocess output collector with timeout, kill, and rejection cleanup
- bun-spawn-shim hardened: Node path forces windowsHide: true; spawn errors no longer escape as unhandledRejection
Foundation for #3919 fix.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
OpenCode Desktop's Electron sidecar runtime can omit Bun's ctx.$ helper.
The sender previously called ctx.$ unconditionally, throwing
TypeError: ctx.$ is not a function as unhandledRejection and crashing
the sidecar with exit code 1.
Add a runtime guard at every call site, falling back to Node.js
child_process.execFile (with windowsHide: true) when ctx.$ is missing.
The Bun ctx.$ path remains preferred when available. Every notification
path is wrapped in try/catch so no failure escapes as unhandledRejection.
Fixes#4128Fixes#4061
The migration log message previously pointed users to docs/reference/configuration.md for the new LSP config location, but that doc section still showed the obsolete plugin-level 'lsp' block. A user following the guidance would re-add the same 'lsp' key, see it stripped again on next startup, and never reach a usable config.\n\nFix both sides: rewrite the log message so it is self-contained (states the new path .opencode/lsp.json and the consumer directly) and rewrite the LSP section in docs/reference/configuration.md to describe the actual current architecture (LSP served by the 'lsp' MCP server, reading server map from .opencode/lsp.json via LSP_TOOLS_MCP_PROJECT_CONFIG, schema lives in packages/lsp-tools-mcp).\n\nVerification: bun test src/shared/migration/ -> 26/26 pass. bun run typecheck -> exit 0. Manual probe -> migration still strips lsp from both in-memory and persisted file.
Issue 1: hasNewCommentsOnly() now returns false when oldString and newString
both contain comment syntax and the new lines are a subset of old lines —
preventing the hook from firing on comment-only modifications.
Issue 2: Per-session deduplication via sessionLastWarning Map with a 30s
window (DEDUP_WINDOW_MS). At most one warning fires per session per
response turn, breaking the deadloop on consecutive edits.
The lsp config block was removed from OhMyOpenCodeConfigSchema when LSP tools were migrated from native plugin tools to the lsp tier-1 MCP server (packages/lsp-tools-mcp). Zod v4 strips unknown keys silently on safeParse, so a v3-era oh-my-opencode.jsonc with 'lsp': { typescript: { command: ... } } continues to live in the file unchanged while doing absolutely nothing. The user reporting #4225 saw their custom LSP servers stop working with zero indication that the configuration site moved.\n\nAdd a migrator that removes the orphan lsp key during migrateConfigFile, mirroring the existing omo_agent -> sisyphus_agent migration immediately above and the 'Removed obsolete hooks from disabled_hooks' precedent below. A single log line records the configPath and the list of dropped server keys so the user has a paper trail in oh-my-opencode.log, and needsWrite is flipped so the cleanup persists to disk (with a timestamped backup) the next time the plugin loads.\n\nReproduction (clean upstream/dev, BEFORE fix):\n needsWrite=false\n inMemory.lsp=<original block, kept>\n persisted.lsp=<original block, kept>\n\nVerification (AFTER fix):\n needsWrite=true\n inMemory.lsp=undefined\n persisted.lsp=undefined\n\nbun test src/shared/migration/ -> 26/26 pass (24/24 pre-existing + 2 new regression tests). bun run typecheck -> exit 0.
The dist-side bug reported in #4220 (`path.endsWith(\"dist/cli.js\")` failing on
Windows backslash paths) has already been fixed in source by routing through
`hasCliSuffix` in `src/mcp/ast-grep.ts` and `src/mcp/lsp.ts`. The `cli-suffix.test.ts`
covered the lsp-tools-mcp shape but not the ast_grep shape.
Adds a regression case that asserts a Windows-style absolute path containing
`...\\packages\\ast-grep-mcp\\dist\\cli.js` matches both `\"dist/cli.js\"` and the
fully-qualified `\"packages/ast-grep-mcp/dist/cli.js\"` suffix — closing the
exact symptom from the bug report against future regressions.
`openai/gpt-5.3-codex` is the codex-series powerhouse still recommended
in docs/guide/agent-model-matching.md and listed in the default
fallback chain in docs/reference/configuration.md, not a deprecated
alias for `gpt-5.4`. The migration entry silently rewrote any user
config that picked `gpt-5.3-codex` for its token efficiency, sending
agents to a non-codex model on every startup.
Drop the bogus mapping from MODEL_VERSION_MAP and add a regression
test that explicit `gpt-5.3-codex` selections (including in nested
fallback_models) survive `migrateModelVersions`. Users already
auto-migrated previously can revert to `gpt-5.3-codex` by hand and it
will now stick on subsequent loads regardless of the sidecar history.