headroom/plugins/opencode/src/plugin.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

71 lines
1.9 KiB
TypeScript
Raw Permalink Normal View History

feat: headroom wrap opencode / unwrap opencode CLI (#1105) ## Summary This PR implements transparent `headroom wrap opencode` support without asking users to edit OpenCode provider URLs, choose an extra CLI flag, or maintain a static provider list. The wrapper now lives at the runtime transport boundary: OpenCode keeps its user/provider config, while Headroom intercepts outbound provider traffic in-process and routes it through the local Headroom proxy. ## What changed ### Transparent OpenCode wrapping - `headroom wrap opencode` injects the `headroom-opencode` plugin through `OPENCODE_CONFIG_CONTENT`. - Existing OpenCode provider URLs are preserved. We do not rewrite user config URLs to point at Headroom. - Existing `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` env vars are preserved. - Local OpenCode traffic, localhost traffic, and Headroom proxy traffic bypass the shim to avoid loops. ### Runtime transport interception - Added an OpenCode plugin transport shim that wraps: - `globalThis.fetch` - `http.request` / `http.get` - `https.request` / `https.get` - External provider calls are routed to the local Headroom proxy. - The original upstream origin is passed through `x-headroom-base-url`, so the proxy can forward to the real provider without changing OpenCode config. - External `http2.connect` is blocked loudly instead of allowing direct provider traffic to leak outside Headroom. ### Live provider additions Provider coverage is no longer based on a static config scan. Because routing happens at outbound request time, providers added mid-session are routed through Headroom automatically as long as they use the covered Node transport paths. ### Subagent and child-process coverage - The parent OpenCode plugin sets a packaged Node preload shim through `NODE_OPTIONS=--import=.../hook-shim/handler.js`. - The transport shim patches `child_process.spawn`, `exec`, `execFile`, and `fork` so child Node processes receive the Headroom preload even when OpenCode passes a custom `env`. - The child-process shim fails closed if it loads without `HEADROOM_OPENCODE_TRANSPORT_PROXY_URL`. - This closes the subagent leak path where a child Node process could otherwise start without Headroom transport interception. ## Why this goes beyond PR #1089 PR #1089 improves OpenCode provider registration, but it still focuses on provider config shape. This PR moves the enforcement boundary to runtime transport interception. This PR goes further because: - No provider URL rewriting is required. - New providers added mid-session are covered automatically. - Subagents and child Node processes inherit the Headroom transport shim. - Direct external HTTP/2 paths fail loudly instead of leaking. - The wrap remains transparent to the user's OpenCode provider config. - The wrapper is fail-closed for unsupported child-process preload state. ## Additional robustness fixes While validating the change in Docker, the full Python suite exposed unrelated Linux/container robustness issues. These are fixed in this PR so the suite is green: - Binary cache handling now treats cache paths under a non-writable existing parent as unavailable, including when tests run as root in Docker. - `release_version.py` honors `MANUAL_VER` before git calls so direct script execution works outside a `.git` checkout. - Test logger isolation now resets relevant Headroom child loggers so proxy logging setup cannot poison later `caplog` tests. - The scanner missing-path test now uses a guaranteed missing `tmp_path` child instead of relying on `/nonexistent/path`. ## Validation All implementation validation was run inside Docker. - Full Python suite from a fresh Docker copy: `6605 passed, 523 skipped`. - Ruff on changed Python/OpenCode paths: passed. - OpenCode plugin typecheck: passed. - OpenCode plugin tests: `9 passed`. - OpenCode plugin build: passed. - Hook shim preload smoke test: passed. ## Notes This PR intentionally does not add a CLI option. `headroom wrap opencode` means full wrap. Either Headroom wraps OpenCode transparently, or the path fails loudly instead of silently leaking provider traffic. --------- Co-authored-by: Rudimar Ronsoni <6081613+rudironsoni@users.noreply.github.com>
2026-06-22 18:07:12 +02:00
import type { Plugin } from "@opencode-ai/plugin";
import { tool } from "@opencode-ai/plugin";
import { z } from "zod";
import { createHeadroomRetrieveTool, getDefaultProxyUrl } from "./retrieve.js";
import { installHeadroomTransport } from "./transport.js";
export interface HeadroomOpenCodePluginOptions {
proxyUrl?: string;
project?: string;
backend?: string;
debug?: boolean;
}
function normalizeProxyUrl(url: string): string {
return url.replace(/\/+$/, "");
}
function resolveProxyUrl(options?: HeadroomOpenCodePluginOptions): string {
return normalizeProxyUrl(
options?.proxyUrl ??
process.env.HEADROOM_PROXY_URL ??
process.env.HEADROOM_BASE_URL ??
getDefaultProxyUrl(),
);
}
export const HeadroomPlugin: Plugin = async (input, options = {}) => {
const pluginOptions = options as HeadroomOpenCodePluginOptions;
const proxyUrl = resolveProxyUrl(pluginOptions);
fix(opencode): send x-headroom-project header on all proxied requests (#2868) ## Description The OpenCode transport plugin set `HEADROOM_PROJECT` as a shell env var for child processes but never forwarded it as `x-headroom-project` on the actual proxied HTTP requests. The proxy's `classify_project` only attributes traffic via `x-headroom-project` header or `/p/<name>` URL prefix — without the header, every OpenCode request was unattributed and the Per-Project Savings dashboard showed `0 project(s)` permanently. Fixes #2847. ## Root cause `installHeadroomTransport` was called with only `{ proxyUrl, debug }`. The `project` value was computed and used only in the `shell.env` hook (for subprocess env injection), never threaded through to `mergeFetchHeaders` or `headersForNodeRequest`. ## Changes Made 1. Add `project?: string` to `InstallOptions` and `TransportState`. 2. Resolve the project value once at plugin init (`pluginOptions.project → input.project.id → input.directory`) and pass it to `installHeadroomTransport`. 3. Both header-building seams now set `x-headroom-project` when a project is present: - `mergeFetchHeaders` (wrapped `fetch` path) - `headersForNodeRequest` (wrapped `http.request` / `https.request` path) 4. Reuse the resolved `project` in the `shell.env` hook (removes the duplicate resolution that was there before). ## Changes - `plugins/opencode/src/transport.ts` — `InstallOptions.project`, `TransportState.project`; `mergeFetchHeaders`, `headersForNodeRequest`, `routedNodeOptions`, `withRoutedFetchInput`, `installHeadroomTransport` updated - `plugins/opencode/src/plugin.ts` — resolve `project` once, pass it to transport; reuse in `shell.env` - `plugins/opencode/src/transport.test.ts` — 3 new tests: project header on fetch, project header on https.request, no header when project unset - `headroom/providers/opencode/_dist/entry.opencode.js` — rebuilt with `npm run build:standalone` to match source ## Testing - [x] Unit tests pass - [x] TypeScript typecheck passes - [x] New regression tests added ### Test Output ``` cd plugins/opencode && npm test # 17 passed (14 existing + 3 new) ``` TypeScript build also passes: `npm run typecheck` (no errors). ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature - [ ] Breaking change - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring ## Real Behavior Proof - Environment: OpenCode transport plugin test environment on the current PR head. - Exact command / steps: ran the plugin test suite and TypeScript typecheck after rebuilding the standalone bundle. - Observed result: all 17 tests passed, including project-header coverage for fetch and Node HTTPS paths plus the unset-project control; typechecking passed. - Not tested: a live OpenCode session against a deployed Headroom proxy. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review --------- Signed-off-by: Radhakrishnan P <gingeekrishna@gmail.com> Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
2026-08-18 08:51:24 +05:30
const project =
pluginOptions.project ??
(input.project as { id?: string } | undefined)?.id ??
input.directory;
feat: headroom wrap opencode / unwrap opencode CLI (#1105) ## Summary This PR implements transparent `headroom wrap opencode` support without asking users to edit OpenCode provider URLs, choose an extra CLI flag, or maintain a static provider list. The wrapper now lives at the runtime transport boundary: OpenCode keeps its user/provider config, while Headroom intercepts outbound provider traffic in-process and routes it through the local Headroom proxy. ## What changed ### Transparent OpenCode wrapping - `headroom wrap opencode` injects the `headroom-opencode` plugin through `OPENCODE_CONFIG_CONTENT`. - Existing OpenCode provider URLs are preserved. We do not rewrite user config URLs to point at Headroom. - Existing `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` env vars are preserved. - Local OpenCode traffic, localhost traffic, and Headroom proxy traffic bypass the shim to avoid loops. ### Runtime transport interception - Added an OpenCode plugin transport shim that wraps: - `globalThis.fetch` - `http.request` / `http.get` - `https.request` / `https.get` - External provider calls are routed to the local Headroom proxy. - The original upstream origin is passed through `x-headroom-base-url`, so the proxy can forward to the real provider without changing OpenCode config. - External `http2.connect` is blocked loudly instead of allowing direct provider traffic to leak outside Headroom. ### Live provider additions Provider coverage is no longer based on a static config scan. Because routing happens at outbound request time, providers added mid-session are routed through Headroom automatically as long as they use the covered Node transport paths. ### Subagent and child-process coverage - The parent OpenCode plugin sets a packaged Node preload shim through `NODE_OPTIONS=--import=.../hook-shim/handler.js`. - The transport shim patches `child_process.spawn`, `exec`, `execFile`, and `fork` so child Node processes receive the Headroom preload even when OpenCode passes a custom `env`. - The child-process shim fails closed if it loads without `HEADROOM_OPENCODE_TRANSPORT_PROXY_URL`. - This closes the subagent leak path where a child Node process could otherwise start without Headroom transport interception. ## Why this goes beyond PR #1089 PR #1089 improves OpenCode provider registration, but it still focuses on provider config shape. This PR moves the enforcement boundary to runtime transport interception. This PR goes further because: - No provider URL rewriting is required. - New providers added mid-session are covered automatically. - Subagents and child Node processes inherit the Headroom transport shim. - Direct external HTTP/2 paths fail loudly instead of leaking. - The wrap remains transparent to the user's OpenCode provider config. - The wrapper is fail-closed for unsupported child-process preload state. ## Additional robustness fixes While validating the change in Docker, the full Python suite exposed unrelated Linux/container robustness issues. These are fixed in this PR so the suite is green: - Binary cache handling now treats cache paths under a non-writable existing parent as unavailable, including when tests run as root in Docker. - `release_version.py` honors `MANUAL_VER` before git calls so direct script execution works outside a `.git` checkout. - Test logger isolation now resets relevant Headroom child loggers so proxy logging setup cannot poison later `caplog` tests. - The scanner missing-path test now uses a guaranteed missing `tmp_path` child instead of relying on `/nonexistent/path`. ## Validation All implementation validation was run inside Docker. - Full Python suite from a fresh Docker copy: `6605 passed, 523 skipped`. - Ruff on changed Python/OpenCode paths: passed. - OpenCode plugin typecheck: passed. - OpenCode plugin tests: `9 passed`. - OpenCode plugin build: passed. - Hook shim preload smoke test: passed. ## Notes This PR intentionally does not add a CLI option. `headroom wrap opencode` means full wrap. Either Headroom wraps OpenCode transparently, or the path fails loudly instead of silently leaking provider traffic. --------- Co-authored-by: Rudimar Ronsoni <6081613+rudironsoni@users.noreply.github.com>
2026-06-22 18:07:12 +02:00
const retrieveTool = createHeadroomRetrieveTool({ proxyBaseUrl: proxyUrl });
const uninstallTransport = installHeadroomTransport({
proxyUrl,
fix(opencode): send x-headroom-project header on all proxied requests (#2868) ## Description The OpenCode transport plugin set `HEADROOM_PROJECT` as a shell env var for child processes but never forwarded it as `x-headroom-project` on the actual proxied HTTP requests. The proxy's `classify_project` only attributes traffic via `x-headroom-project` header or `/p/<name>` URL prefix — without the header, every OpenCode request was unattributed and the Per-Project Savings dashboard showed `0 project(s)` permanently. Fixes #2847. ## Root cause `installHeadroomTransport` was called with only `{ proxyUrl, debug }`. The `project` value was computed and used only in the `shell.env` hook (for subprocess env injection), never threaded through to `mergeFetchHeaders` or `headersForNodeRequest`. ## Changes Made 1. Add `project?: string` to `InstallOptions` and `TransportState`. 2. Resolve the project value once at plugin init (`pluginOptions.project → input.project.id → input.directory`) and pass it to `installHeadroomTransport`. 3. Both header-building seams now set `x-headroom-project` when a project is present: - `mergeFetchHeaders` (wrapped `fetch` path) - `headersForNodeRequest` (wrapped `http.request` / `https.request` path) 4. Reuse the resolved `project` in the `shell.env` hook (removes the duplicate resolution that was there before). ## Changes - `plugins/opencode/src/transport.ts` — `InstallOptions.project`, `TransportState.project`; `mergeFetchHeaders`, `headersForNodeRequest`, `routedNodeOptions`, `withRoutedFetchInput`, `installHeadroomTransport` updated - `plugins/opencode/src/plugin.ts` — resolve `project` once, pass it to transport; reuse in `shell.env` - `plugins/opencode/src/transport.test.ts` — 3 new tests: project header on fetch, project header on https.request, no header when project unset - `headroom/providers/opencode/_dist/entry.opencode.js` — rebuilt with `npm run build:standalone` to match source ## Testing - [x] Unit tests pass - [x] TypeScript typecheck passes - [x] New regression tests added ### Test Output ``` cd plugins/opencode && npm test # 17 passed (14 existing + 3 new) ``` TypeScript build also passes: `npm run typecheck` (no errors). ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature - [ ] Breaking change - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring ## Real Behavior Proof - Environment: OpenCode transport plugin test environment on the current PR head. - Exact command / steps: ran the plugin test suite and TypeScript typecheck after rebuilding the standalone bundle. - Observed result: all 17 tests passed, including project-header coverage for fetch and Node HTTPS paths plus the unset-project control; typechecking passed. - Not tested: a live OpenCode session against a deployed Headroom proxy. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review --------- Signed-off-by: Radhakrishnan P <gingeekrishna@gmail.com> Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
2026-08-18 08:51:24 +05:30
project,
feat: headroom wrap opencode / unwrap opencode CLI (#1105) ## Summary This PR implements transparent `headroom wrap opencode` support without asking users to edit OpenCode provider URLs, choose an extra CLI flag, or maintain a static provider list. The wrapper now lives at the runtime transport boundary: OpenCode keeps its user/provider config, while Headroom intercepts outbound provider traffic in-process and routes it through the local Headroom proxy. ## What changed ### Transparent OpenCode wrapping - `headroom wrap opencode` injects the `headroom-opencode` plugin through `OPENCODE_CONFIG_CONTENT`. - Existing OpenCode provider URLs are preserved. We do not rewrite user config URLs to point at Headroom. - Existing `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` env vars are preserved. - Local OpenCode traffic, localhost traffic, and Headroom proxy traffic bypass the shim to avoid loops. ### Runtime transport interception - Added an OpenCode plugin transport shim that wraps: - `globalThis.fetch` - `http.request` / `http.get` - `https.request` / `https.get` - External provider calls are routed to the local Headroom proxy. - The original upstream origin is passed through `x-headroom-base-url`, so the proxy can forward to the real provider without changing OpenCode config. - External `http2.connect` is blocked loudly instead of allowing direct provider traffic to leak outside Headroom. ### Live provider additions Provider coverage is no longer based on a static config scan. Because routing happens at outbound request time, providers added mid-session are routed through Headroom automatically as long as they use the covered Node transport paths. ### Subagent and child-process coverage - The parent OpenCode plugin sets a packaged Node preload shim through `NODE_OPTIONS=--import=.../hook-shim/handler.js`. - The transport shim patches `child_process.spawn`, `exec`, `execFile`, and `fork` so child Node processes receive the Headroom preload even when OpenCode passes a custom `env`. - The child-process shim fails closed if it loads without `HEADROOM_OPENCODE_TRANSPORT_PROXY_URL`. - This closes the subagent leak path where a child Node process could otherwise start without Headroom transport interception. ## Why this goes beyond PR #1089 PR #1089 improves OpenCode provider registration, but it still focuses on provider config shape. This PR moves the enforcement boundary to runtime transport interception. This PR goes further because: - No provider URL rewriting is required. - New providers added mid-session are covered automatically. - Subagents and child Node processes inherit the Headroom transport shim. - Direct external HTTP/2 paths fail loudly instead of leaking. - The wrap remains transparent to the user's OpenCode provider config. - The wrapper is fail-closed for unsupported child-process preload state. ## Additional robustness fixes While validating the change in Docker, the full Python suite exposed unrelated Linux/container robustness issues. These are fixed in this PR so the suite is green: - Binary cache handling now treats cache paths under a non-writable existing parent as unavailable, including when tests run as root in Docker. - `release_version.py` honors `MANUAL_VER` before git calls so direct script execution works outside a `.git` checkout. - Test logger isolation now resets relevant Headroom child loggers so proxy logging setup cannot poison later `caplog` tests. - The scanner missing-path test now uses a guaranteed missing `tmp_path` child instead of relying on `/nonexistent/path`. ## Validation All implementation validation was run inside Docker. - Full Python suite from a fresh Docker copy: `6605 passed, 523 skipped`. - Ruff on changed Python/OpenCode paths: passed. - OpenCode plugin typecheck: passed. - OpenCode plugin tests: `9 passed`. - OpenCode plugin build: passed. - Hook shim preload smoke test: passed. ## Notes This PR intentionally does not add a CLI option. `headroom wrap opencode` means full wrap. Either Headroom wraps OpenCode transparently, or the path fails loudly instead of silently leaking provider traffic. --------- Co-authored-by: Rudimar Ronsoni <6081613+rudironsoni@users.noreply.github.com>
2026-06-22 18:07:12 +02:00
debug: pluginOptions.debug,
});
return {
dispose: async () => {
uninstallTransport();
},
tool: {
headroom_retrieve: tool({
description: retrieveTool.description,
args: {
hash: z
.string()
.regex(/^[a-f0-9]{24}$/i, "Expected 24-character hex hash"),
},
async execute(args) {
return retrieveTool.execute(args);
},
}),
},
"shell.env": async (_input, output) => {
output.env.HEADROOM_ACTIVE = "1";
output.env.HEADROOM_PROXY_URL = proxyUrl;
fix(opencode): send x-headroom-project header on all proxied requests (#2868) ## Description The OpenCode transport plugin set `HEADROOM_PROJECT` as a shell env var for child processes but never forwarded it as `x-headroom-project` on the actual proxied HTTP requests. The proxy's `classify_project` only attributes traffic via `x-headroom-project` header or `/p/<name>` URL prefix — without the header, every OpenCode request was unattributed and the Per-Project Savings dashboard showed `0 project(s)` permanently. Fixes #2847. ## Root cause `installHeadroomTransport` was called with only `{ proxyUrl, debug }`. The `project` value was computed and used only in the `shell.env` hook (for subprocess env injection), never threaded through to `mergeFetchHeaders` or `headersForNodeRequest`. ## Changes Made 1. Add `project?: string` to `InstallOptions` and `TransportState`. 2. Resolve the project value once at plugin init (`pluginOptions.project → input.project.id → input.directory`) and pass it to `installHeadroomTransport`. 3. Both header-building seams now set `x-headroom-project` when a project is present: - `mergeFetchHeaders` (wrapped `fetch` path) - `headersForNodeRequest` (wrapped `http.request` / `https.request` path) 4. Reuse the resolved `project` in the `shell.env` hook (removes the duplicate resolution that was there before). ## Changes - `plugins/opencode/src/transport.ts` — `InstallOptions.project`, `TransportState.project`; `mergeFetchHeaders`, `headersForNodeRequest`, `routedNodeOptions`, `withRoutedFetchInput`, `installHeadroomTransport` updated - `plugins/opencode/src/plugin.ts` — resolve `project` once, pass it to transport; reuse in `shell.env` - `plugins/opencode/src/transport.test.ts` — 3 new tests: project header on fetch, project header on https.request, no header when project unset - `headroom/providers/opencode/_dist/entry.opencode.js` — rebuilt with `npm run build:standalone` to match source ## Testing - [x] Unit tests pass - [x] TypeScript typecheck passes - [x] New regression tests added ### Test Output ``` cd plugins/opencode && npm test # 17 passed (14 existing + 3 new) ``` TypeScript build also passes: `npm run typecheck` (no errors). ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature - [ ] Breaking change - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring ## Real Behavior Proof - Environment: OpenCode transport plugin test environment on the current PR head. - Exact command / steps: ran the plugin test suite and TypeScript typecheck after rebuilding the standalone bundle. - Observed result: all 17 tests passed, including project-header coverage for fetch and Node HTTPS paths plus the unset-project control; typechecking passed. - Not tested: a live OpenCode session against a deployed Headroom proxy. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review --------- Signed-off-by: Radhakrishnan P <gingeekrishna@gmail.com> Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
2026-08-18 08:51:24 +05:30
output.env.HEADROOM_PROJECT = project;
feat: headroom wrap opencode / unwrap opencode CLI (#1105) ## Summary This PR implements transparent `headroom wrap opencode` support without asking users to edit OpenCode provider URLs, choose an extra CLI flag, or maintain a static provider list. The wrapper now lives at the runtime transport boundary: OpenCode keeps its user/provider config, while Headroom intercepts outbound provider traffic in-process and routes it through the local Headroom proxy. ## What changed ### Transparent OpenCode wrapping - `headroom wrap opencode` injects the `headroom-opencode` plugin through `OPENCODE_CONFIG_CONTENT`. - Existing OpenCode provider URLs are preserved. We do not rewrite user config URLs to point at Headroom. - Existing `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` env vars are preserved. - Local OpenCode traffic, localhost traffic, and Headroom proxy traffic bypass the shim to avoid loops. ### Runtime transport interception - Added an OpenCode plugin transport shim that wraps: - `globalThis.fetch` - `http.request` / `http.get` - `https.request` / `https.get` - External provider calls are routed to the local Headroom proxy. - The original upstream origin is passed through `x-headroom-base-url`, so the proxy can forward to the real provider without changing OpenCode config. - External `http2.connect` is blocked loudly instead of allowing direct provider traffic to leak outside Headroom. ### Live provider additions Provider coverage is no longer based on a static config scan. Because routing happens at outbound request time, providers added mid-session are routed through Headroom automatically as long as they use the covered Node transport paths. ### Subagent and child-process coverage - The parent OpenCode plugin sets a packaged Node preload shim through `NODE_OPTIONS=--import=.../hook-shim/handler.js`. - The transport shim patches `child_process.spawn`, `exec`, `execFile`, and `fork` so child Node processes receive the Headroom preload even when OpenCode passes a custom `env`. - The child-process shim fails closed if it loads without `HEADROOM_OPENCODE_TRANSPORT_PROXY_URL`. - This closes the subagent leak path where a child Node process could otherwise start without Headroom transport interception. ## Why this goes beyond PR #1089 PR #1089 improves OpenCode provider registration, but it still focuses on provider config shape. This PR moves the enforcement boundary to runtime transport interception. This PR goes further because: - No provider URL rewriting is required. - New providers added mid-session are covered automatically. - Subagents and child Node processes inherit the Headroom transport shim. - Direct external HTTP/2 paths fail loudly instead of leaking. - The wrap remains transparent to the user's OpenCode provider config. - The wrapper is fail-closed for unsupported child-process preload state. ## Additional robustness fixes While validating the change in Docker, the full Python suite exposed unrelated Linux/container robustness issues. These are fixed in this PR so the suite is green: - Binary cache handling now treats cache paths under a non-writable existing parent as unavailable, including when tests run as root in Docker. - `release_version.py` honors `MANUAL_VER` before git calls so direct script execution works outside a `.git` checkout. - Test logger isolation now resets relevant Headroom child loggers so proxy logging setup cannot poison later `caplog` tests. - The scanner missing-path test now uses a guaranteed missing `tmp_path` child instead of relying on `/nonexistent/path`. ## Validation All implementation validation was run inside Docker. - Full Python suite from a fresh Docker copy: `6605 passed, 523 skipped`. - Ruff on changed Python/OpenCode paths: passed. - OpenCode plugin typecheck: passed. - OpenCode plugin tests: `9 passed`. - OpenCode plugin build: passed. - Hook shim preload smoke test: passed. ## Notes This PR intentionally does not add a CLI option. `headroom wrap opencode` means full wrap. Either Headroom wraps OpenCode transparently, or the path fails loudly instead of silently leaking provider traffic. --------- Co-authored-by: Rudimar Ronsoni <6081613+rudironsoni@users.noreply.github.com>
2026-06-22 18:07:12 +02:00
if (pluginOptions.backend) {
output.env.HEADROOM_BACKEND = pluginOptions.backend;
}
},
};
};
export default HeadroomPlugin;