OpenCode injects native execution agents like build (and a demoted plan in OMO mode) as { mode: 'subagent', hidden: true }. The dynamic agent discovery in subagent-discovery.ts only filtered by mode, so a hidden agent still resolved as a callable target via task(). This created a boundary leak: an OMO orchestrator (sisyphus, prometheus, etc.) could delegate work into the hidden native build/plan path instead of the OMO category/skill pipeline.
Add hidden?: boolean to AgentInfo, plumb it through mergeWithClaudeCodeAgents, and skip hidden agents in both findCallableAgentMatch and listCallableAgentNames so hidden natives are neither matched nor advertised in 'Available agents' error messages. The OpenCode SDK Agent type already exposes hidden?: boolean, so no schema work is required.
Verified by adding three regression tests in zauc-mocks-subagent-resolver/subagent-resolver.test.ts: hidden 'build' is rejected, hidden 'plan' is rejected, and hidden agents are excluded from the Available agents list. Full delegate-task suite (395 tests) and call-omo-agent suite (57 tests) pass; bun run typecheck is clean.
Prevent delayed loop-start message counts from overwriting active Ralph Loop state after the loop has already advanced, so ULW completion can still enter Oracle verification instead of iterating forever.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Cubic AI reviewer flagged the use of the untrusted Host header as the
URL base in startCallbackServer. The server only binds to 127.0.0.1,
so hardcoding "http://127.0.0.1" as the URL base is robust against
malformed or manipulated Host values and matches upstream behavior
prior to the node:http refactor.
The dist-bundle regression tests in
src/shared/dist-bundle-bun-globals.test.ts are guarded by
`test.skipIf(!existsSync("dist/index.js"))` and dist/ is
gitignored, so they silently skipped in CI which ran tests before
the build step. Adding the build step earlier ensures the
regression guard runs and a future raw `Bun.*` leak in the bundle
fails CI.
Existing test only checked `globalThis.Bun` top-level destructures
and `__require` calls. Add two new test cases:
1. Raw Bun runtime API scanner: scans dist/index.js for any
`Bun.<anyMethod>(` or `Bun.<anyMethod>.` call outside shim-safe
patterns (runtime.Bun, globalThis.Bun, typeof Bun, and the
"Bun is not defined" error-message string). Uses a negative
lookbehind so shim indirection (`runtime.Bun.foo`) passes.
2. Node smoke test: imports dist/index.js under
`node --input-type=module` and asserts stderr contains no
`ReferenceError` and no `Bun is not defined`. The existing
case 3 only checked exit code, which masked lazy-evaluation
crashes that fire after import. Reading exports forces lazy
module-level evaluation paths to run.
bun-install.ts streamToText() was reachable from the plugin bundle via
the cli/config-manager barrel re-export. Replace with the WHATWG
standard `new Response(stream).text()` pattern which works
identically in Bun and Node and avoids the last raw Bun.* runtime
call in dist/index.js.
The OAuth callback server was using Bun.serve, which would crash if
mcp-oauth code paths ever entered the plugin bundle. Switch to
node:http.createServer with the same WHATWG behavior:
- Binds to 127.0.0.1, preserves 200/400/404 status codes
- Translates fetch(Request)→Response to (req, res) callback style
- Clears OAuth timeout on success/error/missing-param paths
- Replaces server.stop(true) with server.close() for shutdown
Functional behavior and response bodies unchanged.
isPortAvailable() previously bound a one-shot Bun.serve and stopped it.
That call was reachable from the plugin bundle through
src/shared/index.ts barrel re-export and crashed on Electron.
Switch to node:net.createServer().listen(port, host), which Bun fully
implements as well. Adds a 2s safety timeout and removes both
"error" and "listening" handlers on resolution to prevent listener
leaks. Behavior is bit-equivalent: returns true iff a server can bind
to (host, port) right now.
Test file is fully rewritten away from stale Bun.serve mocking. New
tests exercise: free-port detection via port 0, EADDRINUSE handling
via a real net.createServer blocker, findAvailablePort range
exhaustion, getAvailableServerPort auto-selection, 127.0.0.1 default
hostname binding, and probe-server resource cleanup.
Eliminates 19 unguarded `Bun.*` runtime call sites in the plugin bundle
that crashed with `ReferenceError: Bun is not defined` under Electron.
Per-tool-call hot paths (executed on every Read/Edit):
- src/tools/hashline-edit/hash-computation.ts: Bun.hash.xxHash32 → bunHashXxh32
- src/tools/hashline-edit/hashline-edit-executor.ts: 8 sites via bunFile/bunWrite
- src/hooks/hashline-read-enhancer/hook.ts: Bun.file → bunFile
- src/hooks/hashline-edit-diff-enhancer/hook.ts: 2 sites via bunFile
Plugin-load paths:
- src/hooks/claude-code-hooks/config.ts and config-loader.ts: Bun.file → bunFile
- src/features/claude-code-mcp-loader/loader.ts: Bun.file → bunFile
- src/features/claude-code-plugin-loader/mcp-server-loader.ts: Bun.file → bunFile
- src/features/team-mode/deps.ts: Bun.spawn → spawn shim
- src/hooks/session-notification-utils.ts: Bun.which → bunWhich, also drops
the bare `declare const Bun` ambient declaration
- src/shared/binary-downloader.ts: Bun.write → bunWrite
Pure mechanical API swaps. No control-flow or signature changes.
The plugin builds with `bun build --target bun` and runs under OpenCode
CLI (Bun SEA) but also under OpenCode Desktop (Electron / Node V8) where
`globalThis.Bun` does not exist. Mirror the existing `bun-spawn-shim.ts`
pattern for three more Bun runtime APIs:
- bun-file-shim: bunFile()/bunWrite() backed by node:fs/promises with
ArrayBuffer slicing to avoid Node Buffer pool exposure
- bun-hash-shim: pure-JS XXH32, bit-exact with Bun.hash.xxHash32 verified
by 1200-pair fuzz comparison so existing hashline LINE#ID tags remain
stable across runtimes
- bun-which-shim: synchronous PATH walker with Windows .exe/.cmd/.bat/.com
extensions plus isUnsafeCommandName guard that rejects path separators,
parent traversal, drive letters and null bytes before any probe
Each shim uses the canonical `runtime.Bun !== undefined` detection and
delegates to native Bun under IS_BUN, otherwise uses Node primitives.
Each ships with a co-located test that exercises both branches via the
`node:vm.runInNewContext` pattern from bun-hash-shim.test.ts.