diff --git a/README.md b/README.md
index 971170370..accab2e0e 100644
--- a/README.md
+++ b/README.md
@@ -118,6 +118,8 @@ Freshell indexes local session history and can launch terminals for these coding
Enable/disable providers and set defaults in the Settings UI or via `~/.freshell/config.json`.
OpenCode sessions are discovered directly from OpenCode's local session database, so existing OpenCode work can be resumed from freshell without importing anything manually.
+OpenCode permissions are controlled by the OpenCode configuration for the OS user running freshell. Freshell does not set `OPENCODE_PERMISSION` or pass `--dangerously-skip-permissions` for OpenCode sessions; OS filesystem permissions remain the hard boundary.
+
## Tech Stack
- **Frontend**: React 18, Redux Toolkit, Tailwind CSS, xterm.js, Monaco Editor, Zod, lucide-react
diff --git a/docs/superpowers/plans/2026-05-30-opencode-local-permission-policy.md b/docs/superpowers/plans/2026-05-30-opencode-local-permission-policy.md
new file mode 100644
index 000000000..de0393f07
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-30-opencode-local-permission-policy.md
@@ -0,0 +1,680 @@
+# OpenCode Local Permission Policy Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Make Freshell defer OpenCode permission policy to the OS user's OpenCode configuration instead of setting Freshell-specific OpenCode permission overrides.
+
+**Architecture:** Freshell keeps launching OpenCode and Freshopencode, but stops advertising OpenCode permission controls and stops passing OpenCode permission overrides. Terminal OpenCode panes rely on the OpenCode CLI's resolved config, while Freshopencode `opencode run` calls no longer use `--dangerously-skip-permissions`. Claude and Codex permission behavior stays unchanged.
+
+**Tech Stack:** TypeScript, React 18, Redux Toolkit, Node/Express, Vitest, Testing Library, Freshell extension manifests, OpenCode CLI.
+
+---
+
+## Scope Check
+
+This is one cohesive product change with three surfaces:
+
+- OpenCode terminal panes from the CLI extension manifest.
+- Freshopencode panes from the fresh-agent runtime.
+- User-facing documentation in `README.md`.
+
+Do not edit the user's `~/.config/opencode` or project `.opencode` files in this product change. Machine/user OpenCode policy is configured outside the repo.
+
+## File Structure
+
+- Modify `extensions/opencode/freshell.json`: remove OpenCode permission support metadata so the built-in CLI extension no longer advertises a Freshell permission control or `OPENCODE_PERMISSION` mapping.
+- Modify `server/terminal-registry.ts`: remove the fallback OpenCode `OPENCODE_PERMISSION` mapping used before extension bootstrap so all terminal-launch paths share the same "no Freshell OpenCode policy" contract.
+- Modify `test/unit/server/terminal-registry.test.ts`: replace OpenCode permission-env expectations with regression coverage that permission settings do not affect OpenCode terminal launch specs.
+- Modify `test/integration/extension-system.test.ts`: add built-in manifest coverage proving OpenCode does not expose permission mode support through the extension registry.
+- Modify `src/lib/fresh-agent-registry.ts`: mark Freshopencode permission controls hidden and set its inert default to `default`.
+- Modify `src/components/panes/PaneContainer.tsx`: avoid writing `permissionMode` into newly created Freshopencode pane content.
+- Modify `src/lib/session-type-utils.ts`: avoid writing `permissionMode` into resumed Freshopencode pane content.
+- Modify `src/components/fresh-agent/FreshAgentView.tsx`: avoid sending stale Freshopencode `permissionMode` values in `freshAgent.create` or `freshAgent.send` messages.
+- Modify `test/unit/client/components/panes/PaneContainer.test.tsx`: prove Freshopencode picker-created panes do not carry Freshell permission policy.
+- Modify `test/unit/client/lib/session-type-utils.test.ts`: prove resumed Freshopencode panes do not carry Freshell permission policy.
+- Modify `test/unit/client/components/fresh-agent/FreshAgentView.test.tsx`: prove stale Freshopencode permission fields are not transmitted.
+- Modify `server/fresh-agent/adapters/opencode/adapter.ts`: remove `--dangerously-skip-permissions` from `opencode run`.
+- Modify `test/unit/server/fresh-agent/opencode-adapter.test.ts`: update command expectations to prove Freshopencode does not bypass local OpenCode policy.
+- Modify `README.md`: document that OpenCode permissions are controlled by OpenCode's local config and OS permissions.
+
+## Behavioral Contract
+
+After this change:
+
+- Freshell terminal OpenCode panes must not set `OPENCODE_PERMISSION`.
+- Freshell terminal OpenCode panes must not add a permission-mode CLI arg.
+- Freshopencode must not pass `--dangerously-skip-permissions`.
+- Freshopencode panes must not include or transmit `permissionMode`.
+- OpenCode model, resume, server endpoint, renderer, scroll-input, session discovery, and MCP behavior must continue to work.
+- Claude and Codex permission controls must continue to work.
+
+### Task 1: Terminal OpenCode Defers Permission Policy
+
+**Files:**
+- Modify: `test/unit/server/terminal-registry.test.ts`
+- Modify: `test/integration/extension-system.test.ts`
+- Modify: `extensions/opencode/freshell.json`
+- Modify: `server/terminal-registry.ts`
+
+- [ ] **Step 1: Replace the OpenCode permission-env unit tests with failing no-policy tests**
+
+In `test/unit/server/terminal-registry.test.ts`, replace the two tests named `maps OpenCode plan permission mode to OPENCODE_PERMISSION env` and `maps OpenCode acceptEdits permission mode to OPENCODE_PERMISSION env` with:
+
+```ts
+ it('does not set OPENCODE_PERMISSION for OpenCode when permission mode is provided', () => {
+ delete process.env.OPENCODE_CMD
+
+ const spec = buildSpawnSpec('opencode', '/Users/john/project', 'system', undefined, {
+ permissionMode: 'bypassPermissions',
+ opencodeServer: TEST_OPENCODE_SERVER,
+ })
+
+ expect(spec.env).not.toHaveProperty('OPENCODE_PERMISSION')
+ expect(spec.args).not.toContain('--permission-mode')
+ expect(spec.args).toContain('--hostname')
+ expect(spec.args).toContain('127.0.0.1')
+ expect(spec.args).toContain('--port')
+ expect(spec.args).toContain(String(TEST_OPENCODE_SERVER.port))
+ })
+
+ it('keeps OpenCode model and resume behavior while ignoring permission mode', () => {
+ delete process.env.OPENCODE_CMD
+
+ const spec = buildSpawnSpec('opencode', '/Users/john/project', 'system', 'ses_existing', {
+ permissionMode: 'plan',
+ model: 'openai/gpt-5-mini',
+ opencodeServer: TEST_OPENCODE_SERVER,
+ })
+
+ expect(spec.env).not.toHaveProperty('OPENCODE_PERMISSION')
+ expect(spec.args).toContain('--session')
+ expect(spec.args).toContain('ses_existing')
+ expect(spec.args).not.toContain('--model')
+ expect(spec.args).not.toContain('openai/gpt-5-mini')
+ })
+```
+
+- [ ] **Step 2: Add built-in manifest registry coverage**
+
+In `test/integration/extension-system.test.ts`, add this test near the existing CLI manifest tests:
+
+```ts
+ it('builtin OpenCode extension does not expose Freshell permission controls', () => {
+ const builtinDir = path.resolve(process.cwd(), 'extensions')
+ mgr.scan([builtinDir])
+
+ const opencode = mgr.toClientRegistry().find((entry) => entry.name === 'opencode')
+
+ expect(opencode).toBeDefined()
+ expect(opencode?.label).toBe('OpenCode')
+ expect(opencode?.category).toBe('cli')
+ expect(opencode?.cli?.supportsResume).toBe(true)
+ expect(opencode?.cli?.supportsModel).toBe(true)
+ expect(opencode?.cli?.supportsPermissionMode).toBeFalsy()
+ })
+```
+
+- [ ] **Step 3: Run the focused tests and verify they fail**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/server/terminal-registry.test.ts test/integration/extension-system.test.ts --run
+```
+
+Expected: FAIL. The terminal-registry test still sees `OPENCODE_PERMISSION`, and the built-in manifest still exposes `supportsPermissionMode`.
+
+- [ ] **Step 4: Remove OpenCode permission metadata from the built-in manifest**
+
+Replace `extensions/opencode/freshell.json` with:
+
+```json
+{
+ "name": "opencode",
+ "version": "1.0.0",
+ "label": "OpenCode",
+ "description": "OpenCode CLI agent",
+ "category": "cli",
+ "cli": {
+ "command": "opencode",
+ "envVar": "OPENCODE_CMD",
+ "resumeArgs": ["--session", "{{sessionId}}"],
+ "modelArgs": ["--model", "{{model}}"],
+ "supportsModel": true,
+ "terminalBehavior": {
+ "preferredRenderer": "canvas",
+ "scrollInputPolicy": "native"
+ }
+ },
+ "picker": {
+ "group": "agents"
+ }
+}
+```
+
+- [ ] **Step 5: Remove OpenCode permission metadata from the fallback registry seed**
+
+In `server/terminal-registry.ts`, change the OpenCode fallback entry from:
+
+```ts
+ ['opencode', {
+ label: 'OpenCode',
+ envVar: 'OPENCODE_CMD',
+ defaultCommand: 'opencode',
+ resumeArgs: (sessionId: string) => ['--session', sessionId],
+ modelArgs: (model: string) => ['--model', model],
+ permissionModeEnvVar: 'OPENCODE_PERMISSION',
+ permissionModeEnvValues: {
+ plan: '{"edit":"ask","bash":"ask"}',
+ acceptEdits: '{"edit":"allow","bash":"ask"}',
+ bypassPermissions: '{"edit":"allow","bash":"allow"}',
+ },
+ }],
+```
+
+to:
+
+```ts
+ ['opencode', {
+ label: 'OpenCode',
+ envVar: 'OPENCODE_CMD',
+ defaultCommand: 'opencode',
+ resumeArgs: (sessionId: string) => ['--session', sessionId],
+ modelArgs: (model: string) => ['--model', model],
+ }],
+```
+
+- [ ] **Step 6: Run the focused tests and verify they pass**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/server/terminal-registry.test.ts test/integration/extension-system.test.ts --run
+```
+
+Expected: PASS.
+
+- [ ] **Step 7: Commit Task 1**
+
+```bash
+git add extensions/opencode/freshell.json server/terminal-registry.ts test/unit/server/terminal-registry.test.ts test/integration/extension-system.test.ts
+git commit -m "fix: defer opencode terminal permissions to local config"
+```
+
+### Task 2: Freshopencode Pane State Does Not Carry Freshell Permission Policy
+
+**Files:**
+- Modify: `test/unit/client/components/panes/PaneContainer.test.tsx`
+- Modify: `test/unit/client/lib/session-type-utils.test.ts`
+- Modify: `test/unit/client/components/fresh-agent/FreshAgentView.test.tsx`
+- Modify: `src/lib/fresh-agent-registry.ts`
+- Modify: `src/components/panes/PaneContainer.tsx`
+- Modify: `src/lib/session-type-utils.ts`
+- Modify: `src/components/fresh-agent/FreshAgentView.tsx`
+
+- [ ] **Step 1: Add a picker-created Freshopencode pane-state assertion**
+
+In `test/unit/client/components/panes/PaneContainer.test.tsx`, inside the existing test named `enables Freshopencode from the picker with OpenCode defaults`, add this assertion after the `effort` assertion:
+
+```ts
+ expect(paneContent.permissionMode).toBeUndefined()
+```
+
+- [ ] **Step 2: Add resumed Freshopencode content coverage**
+
+In `test/unit/client/lib/session-type-utils.test.ts`, add this test in the `buildResumeContent` describe block:
+
+```ts
+ it('returns freshopencode resume content without a Freshell permission mode', () => {
+ const content = buildResumeContent({
+ sessionType: 'freshopencode',
+ sessionId: 'ses_opencode_123',
+ cwd: '/home/user/project',
+ agentChatProviderSettings: {
+ defaultPermissionMode: 'bypassPermissions',
+ },
+ })
+
+ expect(content.kind).toBe('fresh-agent')
+ if (content.kind !== 'fresh-agent') throw new Error('expected fresh-agent')
+ expect(content.sessionType).toBe('freshopencode')
+ expect(content.provider).toBe('opencode')
+ expect(content.resumeSessionId).toBe('ses_opencode_123')
+ expect(content.sessionRef).toEqual({
+ provider: 'opencode',
+ sessionId: 'ses_opencode_123',
+ })
+ expect(content.initialCwd).toBe('/home/user/project')
+ expect(content.permissionMode).toBeUndefined()
+ })
+```
+
+- [ ] **Step 3: Add stale Freshopencode transmission coverage**
+
+In `test/unit/client/components/fresh-agent/FreshAgentView.test.tsx`, add this test in the `FreshAgentView` describe block:
+
+```tsx
+ it('does not transmit stale Freshopencode permissionMode on create or send', async () => {
+ const creatingStore = createStore()
+ creatingStore.dispatch(initLayout({
+ tabId: 'tab-1',
+ paneId: 'pane-1',
+ content: {
+ kind: 'fresh-agent',
+ sessionType: 'freshopencode',
+ provider: 'opencode',
+ createRequestId: 'req-opencode-policy',
+ status: 'creating',
+ initialCwd: '/repo',
+ model: 'opencode-go/deepseek-v4-flash',
+ effort: 'max',
+ permissionMode: 'bypassPermissions',
+ },
+ }))
+
+ render(
+
+
+ ,
+ )
+
+ const createMessage = wsMock.send.mock.calls
+ .map(([message]) => message)
+ .find((message) => message?.type === 'freshAgent.create')
+ expect(createMessage).toBeDefined()
+ expect(createMessage).not.toHaveProperty('permissionMode')
+
+ cleanup()
+ wsMock.send.mockClear()
+
+ const sendingStore = createStore()
+ sendingStore.dispatch(initLayout({
+ tabId: 'tab-1',
+ paneId: 'pane-1',
+ content: {
+ kind: 'fresh-agent',
+ sessionType: 'freshopencode',
+ provider: 'opencode',
+ createRequestId: 'req-opencode-send-policy',
+ sessionId: 'freshopencode-req-opencode-send-policy',
+ status: 'idle',
+ initialCwd: '/repo',
+ model: 'opencode-go/deepseek-v4-flash',
+ effort: 'max',
+ permissionMode: 'bypassPermissions',
+ },
+ }))
+
+ render(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name: 'Chat message input' })).not.toBeDisabled()
+ })
+ wsMock.send.mockClear()
+
+ fireEvent.change(screen.getByRole('textbox', { name: 'Chat message input' }), {
+ target: { value: 'Use local OpenCode policy' },
+ })
+ fireEvent.click(screen.getByRole('button', { name: 'Send' }))
+
+ expect(wsMock.send).toHaveBeenCalledWith({
+ type: 'freshAgent.send',
+ sessionId: 'freshopencode-req-opencode-send-policy',
+ sessionType: 'freshopencode',
+ provider: 'opencode',
+ text: 'Use local OpenCode policy',
+ settings: {
+ cwd: '/repo',
+ model: 'opencode-go/deepseek-v4-flash',
+ effort: 'max',
+ },
+ })
+ })
+```
+
+- [ ] **Step 4: Run the focused tests and verify they fail**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/client/components/panes/PaneContainer.test.tsx test/unit/client/lib/session-type-utils.test.ts test/unit/client/components/fresh-agent/FreshAgentView.test.tsx --run
+```
+
+Expected: FAIL. New Freshopencode pane content and outgoing messages still include `permissionMode`.
+
+- [ ] **Step 5: Hide Freshopencode permission controls in the registry**
+
+In `src/lib/fresh-agent-registry.ts`, replace the Freshopencode entry with:
+
+```ts
+ {
+ sessionType: 'freshopencode',
+ runtimeProvider: 'opencode',
+ label: 'Freshopencode',
+ icon: OpencodeIcon,
+ defaultModel: FRESHOPENCODE_DEFAULT_MODEL,
+ defaultPermissionMode: 'default',
+ defaultEffort: FRESHOPENCODE_DEFAULT_EFFORT,
+ settingsVisibility: {
+ model: true,
+ permissionMode: false,
+ effort: true,
+ thinking: true,
+ tools: true,
+ timecodes: true,
+ },
+ pickerShortcut: 'O',
+ pickerAfterCli: true,
+ },
+```
+
+- [ ] **Step 6: Omit permissionMode when creating Freshopencode pane content**
+
+In `src/components/panes/PaneContainer.tsx`, inside the `if (freshAgentType)` branch, introduce a local `permissionMode` before the `return`:
+
+```ts
+ const permissionMode = freshAgentType.settingsVisibility.permissionMode === false
+ ? undefined
+ : providerSettings?.defaultPermissionMode
+ ?? (freshAgentType.runtimeProvider === 'codex'
+ ? settings?.codingCli?.providers?.[freshAgentType.runtimeProvider]?.permissionMode
+ : undefined)
+ ?? providerConfig?.defaultPermissionMode
+ ?? freshAgentType.defaultPermissionMode
+```
+
+Then replace the current `permissionMode: ...` property in the returned object with:
+
+```ts
+ ...(permissionMode ? { permissionMode } : {}),
+```
+
+- [ ] **Step 7: Omit permissionMode when building Freshopencode resume content**
+
+In `src/lib/session-type-utils.ts`, inside the `if (freshAgentType)` branch of `buildResumeContent`, add:
+
+```ts
+ const permissionMode = freshAgentType.settingsVisibility.permissionMode === false
+ ? undefined
+ : ps?.defaultPermissionMode ?? agentConfig?.defaultPermissionMode ?? freshAgentType.defaultPermissionMode
+```
+
+Then replace:
+
+```ts
+ permissionMode: ps?.defaultPermissionMode ?? agentConfig?.defaultPermissionMode ?? freshAgentType.defaultPermissionMode,
+```
+
+with:
+
+```ts
+ ...(permissionMode ? { permissionMode } : {}),
+```
+
+- [ ] **Step 8: Do not transmit Freshopencode permissionMode from FreshAgentView**
+
+In `src/components/fresh-agent/FreshAgentView.tsx`, add this helper near `getEffectiveFreshAgentEffort`:
+
+```ts
+function getEffectiveFreshAgentPermissionMode(content: FreshAgentPaneContent): string | undefined {
+ return content.provider === 'opencode' ? undefined : content.permissionMode
+}
+```
+
+Then update `buildCreateMessage` from:
+
+```ts
+ permissionMode: content.permissionMode,
+```
+
+to:
+
+```ts
+ ...(getEffectiveFreshAgentPermissionMode(content) ? { permissionMode: getEffectiveFreshAgentPermissionMode(content) } : {}),
+```
+
+And update the `freshAgent.send` settings object from:
+
+```ts
+ ...(paneContent.permissionMode ? { permissionMode: paneContent.permissionMode } : {}),
+```
+
+to:
+
+```ts
+ ...(getEffectiveFreshAgentPermissionMode(paneContent) ? { permissionMode: getEffectiveFreshAgentPermissionMode(paneContent) } : {}),
+```
+
+- [ ] **Step 9: Run the focused tests and verify they pass**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/client/components/panes/PaneContainer.test.tsx test/unit/client/lib/session-type-utils.test.ts test/unit/client/components/fresh-agent/FreshAgentView.test.tsx --run
+```
+
+Expected: PASS.
+
+- [ ] **Step 10: Commit Task 2**
+
+```bash
+git add src/lib/fresh-agent-registry.ts src/components/panes/PaneContainer.tsx src/lib/session-type-utils.ts src/components/fresh-agent/FreshAgentView.tsx test/unit/client/components/panes/PaneContainer.test.tsx test/unit/client/lib/session-type-utils.test.ts test/unit/client/components/fresh-agent/FreshAgentView.test.tsx
+git commit -m "fix: remove freshell permission state from freshopencode"
+```
+
+### Task 3: Freshopencode Runs Do Not Bypass OpenCode Local Policy
+
+**Files:**
+- Modify: `test/unit/server/fresh-agent/opencode-adapter.test.ts`
+- Modify: `server/fresh-agent/adapters/opencode/adapter.ts`
+
+- [ ] **Step 1: Update OpenCode adapter command expectations**
+
+In `test/unit/server/fresh-agent/opencode-adapter.test.ts`, replace every expected command key and expected argument list that includes `--dangerously-skip-permissions`.
+
+Use these replacements:
+
+```ts
+'run reply ok --format json --model opencode-go/deepseek-v4-flash --variant max'
+'run first --format json --model opencode-go/glm-5.1 --variant high'
+'run second --format json --session ses_real_2 --model opencode-go/glm-5.1 --variant high'
+'run reply ok --format json --session ses_restored_1'
+'run /compact keep decisions --format json --session ses_restored_1'
+'run reply ok --format json --model opencode-go/deepseek-v4-flash --variant high'
+```
+
+In the first test's `expect(calls[0]).toEqual(...)`, change the expected list to:
+
+```ts
+ expect(calls[0]).toEqual([
+ 'run',
+ 'reply ok',
+ '--format',
+ 'json',
+ '--model',
+ 'opencode-go/deepseek-v4-flash',
+ '--variant',
+ 'max',
+ ])
+```
+
+For the resume/compact test assertions, remove the expected `--dangerously-skip-permissions` entries and keep `--session` expectations intact:
+
+```ts
+ expect(calls[0]).toEqual([
+ 'run',
+ 'reply ok',
+ '--format',
+ 'json',
+ '--session',
+ 'ses_restored_1',
+ ])
+ expect(calls[1]).toEqual([
+ 'run',
+ '/compact keep decisions',
+ '--format',
+ 'json',
+ '--session',
+ 'ses_restored_1',
+ ])
+```
+
+- [ ] **Step 2: Run the focused adapter test and verify it fails**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/server/fresh-agent/opencode-adapter.test.ts --run
+```
+
+Expected: FAIL. The adapter still passes `--dangerously-skip-permissions`.
+
+- [ ] **Step 3: Remove the bypass flag from Freshopencode run args**
+
+In `server/fresh-agent/adapters/opencode/adapter.ts`, change:
+
+```ts
+ const args = [
+ 'run',
+ text,
+ '--format',
+ 'json',
+ '--dangerously-skip-permissions',
+ ...(state.realSessionId ? ['--session', state.realSessionId] : []),
+ ...(model ? ['--model', model] : []),
+ ...(effort ? ['--variant', effort] : []),
+ ]
+```
+
+to:
+
+```ts
+ const args = [
+ 'run',
+ text,
+ '--format',
+ 'json',
+ ...(state.realSessionId ? ['--session', state.realSessionId] : []),
+ ...(model ? ['--model', model] : []),
+ ...(effort ? ['--variant', effort] : []),
+ ]
+```
+
+- [ ] **Step 4: Run the focused adapter test and verify it passes**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/server/fresh-agent/opencode-adapter.test.ts --run
+```
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit Task 3**
+
+```bash
+git add server/fresh-agent/adapters/opencode/adapter.ts test/unit/server/fresh-agent/opencode-adapter.test.ts
+git commit -m "fix: defer freshopencode run permissions to opencode"
+```
+
+### Task 4: Document the Product Contract
+
+**Files:**
+- Modify: `README.md`
+
+- [ ] **Step 1: Add README documentation**
+
+In `README.md`, after the paragraph:
+
+```md
+OpenCode sessions are discovered directly from OpenCode's local session database, so existing OpenCode work can be resumed from freshell without importing anything manually.
+```
+
+add:
+
+```md
+OpenCode permissions are controlled by the OpenCode configuration for the OS user running freshell. Freshell does not set `OPENCODE_PERMISSION` or pass `--dangerously-skip-permissions` for OpenCode sessions; OS filesystem permissions remain the hard boundary.
+```
+
+- [ ] **Step 2: Verify the README wording is present**
+
+Run:
+
+```bash
+rg -n "OpenCode permissions are controlled" README.md
+```
+
+Expected: one matching line.
+
+- [ ] **Step 3: Commit Task 4**
+
+```bash
+git add README.md
+git commit -m "docs: document opencode permission ownership"
+```
+
+### Task 5: Final Verification
+
+**Files:**
+- Verify: all files changed by Tasks 1-4
+
+- [ ] **Step 1: Confirm no product OpenCode permission override remains**
+
+Run:
+
+```bash
+rg -n -e "OPENCODE_PERMISSION" -e "--dangerously-skip-permissions" extensions server src test/unit test/integration README.md
+```
+
+Expected: no matches in `extensions/`, `server/`, `src/`, `test/unit/`, or `test/integration/`. Matches in `test/integration/real/coding-cli-session-contract.test.ts` are acceptable only if the command above is expanded later to include `test/integration/real`, because that file probes the upstream OpenCode CLI directly rather than Freshell's product launch path.
+
+- [ ] **Step 2: Run all focused tests from this plan**
+
+Run:
+
+```bash
+npm run test:vitest -- test/unit/server/terminal-registry.test.ts test/integration/extension-system.test.ts test/unit/client/components/panes/PaneContainer.test.tsx test/unit/client/lib/session-type-utils.test.ts test/unit/client/components/fresh-agent/FreshAgentView.test.tsx test/unit/server/fresh-agent/opencode-adapter.test.ts --run
+```
+
+Expected: PASS.
+
+- [ ] **Step 3: Run the repo-supported final verification**
+
+Run:
+
+```bash
+FRESHELL_TEST_SUMMARY="opencode local permission policy final verification" npm run check
+```
+
+Expected: PASS for typecheck and coordinated full suite.
+
+- [ ] **Step 4: Review the final diff**
+
+Run:
+
+```bash
+git diff --stat origin/main...HEAD
+git diff --check origin/main...HEAD
+```
+
+Expected: changed files match this plan, and `git diff --check` prints no whitespace errors.
+
+- [ ] **Step 5: Confirm the working tree is clean**
+
+Run:
+
+```bash
+git status --short
+```
+
+Expected: clean working tree.
+
+## Self-Review Notes
+
+- Spec coverage: Terminal OpenCode no longer receives Freshell permission env, Freshopencode no longer receives permission state or bypass flags, and README states the product contract.
+- Placeholder scan: This plan contains concrete paths, commands, expected outcomes, and code snippets for every code-changing step.
+- Type consistency: `permissionMode` remains optional in existing pane/message types; the plan omits it for OpenCode instead of introducing a new type variant.
diff --git a/extensions/opencode/freshell.json b/extensions/opencode/freshell.json
index f3b946e43..c9386ac06 100644
--- a/extensions/opencode/freshell.json
+++ b/extensions/opencode/freshell.json
@@ -9,13 +9,6 @@
"envVar": "OPENCODE_CMD",
"resumeArgs": ["--session", "{{sessionId}}"],
"modelArgs": ["--model", "{{model}}"],
- "permissionModeEnvVar": "OPENCODE_PERMISSION",
- "permissionModeValues": {
- "plan": "{\"edit\":\"ask\",\"bash\":\"ask\"}",
- "acceptEdits": "{\"edit\":\"allow\",\"bash\":\"ask\"}",
- "bypassPermissions": "{\"edit\":\"allow\",\"bash\":\"allow\"}"
- },
- "supportsPermissionMode": true,
"supportsModel": true,
"terminalBehavior": {
"preferredRenderer": "canvas",
diff --git a/server/fresh-agent/adapters/opencode/adapter.ts b/server/fresh-agent/adapters/opencode/adapter.ts
index 663d77873..0f5cd28c2 100644
--- a/server/fresh-agent/adapters/opencode/adapter.ts
+++ b/server/fresh-agent/adapters/opencode/adapter.ts
@@ -163,7 +163,6 @@ export function createOpencodeFreshAgentAdapter(options: {
text,
'--format',
'json',
- '--dangerously-skip-permissions',
...(state.realSessionId ? ['--session', state.realSessionId] : []),
...(model ? ['--model', model] : []),
...(effort ? ['--variant', effort] : []),
diff --git a/server/terminal-registry.ts b/server/terminal-registry.ts
index 73633c4db..da3e2701f 100644
--- a/server/terminal-registry.ts
+++ b/server/terminal-registry.ts
@@ -98,12 +98,6 @@ const FALLBACK_CODING_CLI_COMMAND_SPECS: Array<[string, CodingCliCommandSpec]> =
defaultCommand: 'opencode',
resumeArgs: (sessionId: string) => ['--session', sessionId],
modelArgs: (model: string) => ['--model', model],
- permissionModeEnvVar: 'OPENCODE_PERMISSION',
- permissionModeEnvValues: {
- plan: '{"edit":"ask","bash":"ask"}',
- acceptEdits: '{"edit":"allow","bash":"ask"}',
- bypassPermissions: '{"edit":"allow","bash":"allow"}',
- },
}],
['gemini', {
label: 'Gemini',
diff --git a/src/components/fresh-agent/FreshAgentView.tsx b/src/components/fresh-agent/FreshAgentView.tsx
index c7e1a3898..18f418916 100644
--- a/src/components/fresh-agent/FreshAgentView.tsx
+++ b/src/components/fresh-agent/FreshAgentView.tsx
@@ -39,6 +39,10 @@ function getEffectiveFreshAgentEffort(content: FreshAgentPaneContent): string |
return normalizeFreshAgentEffort(content.sessionType, content.provider, getEffectiveFreshAgentModel(content), content.effort)
}
+function getEffectiveFreshAgentPermissionMode(content: FreshAgentPaneContent): string | undefined {
+ return content.provider === 'opencode' ? undefined : content.permissionMode
+}
+
function isStatusRegression(current: string, next: string): boolean {
return !EARLY_STATES.has(current) && EARLY_STATES.has(next)
}
@@ -329,7 +333,7 @@ export function FreshAgentView({
sessionRef: content.sessionRef,
modelSelection: content.modelSelection,
model: getEffectiveFreshAgentModel(content),
- permissionMode: content.permissionMode,
+ ...(getEffectiveFreshAgentPermissionMode(content) ? { permissionMode: getEffectiveFreshAgentPermissionMode(content) } : {}),
sandbox: content.sandbox,
effort: getEffectiveFreshAgentEffort(content),
plugins: content.plugins,
@@ -971,7 +975,7 @@ export function FreshAgentView({
settings: {
...(paneContent.initialCwd ? { cwd: paneContent.initialCwd } : {}),
...(getEffectiveFreshAgentModel(paneContent) ? { model: getEffectiveFreshAgentModel(paneContent) } : {}),
- ...(paneContent.permissionMode ? { permissionMode: paneContent.permissionMode } : {}),
+ ...(getEffectiveFreshAgentPermissionMode(paneContent) ? { permissionMode: getEffectiveFreshAgentPermissionMode(paneContent) } : {}),
...(paneContent.sandbox ? { sandbox: paneContent.sandbox } : {}),
...(getEffectiveFreshAgentEffort(paneContent) ? { effort: getEffectiveFreshAgentEffort(paneContent) } : {}),
},
diff --git a/src/components/panes/PaneContainer.tsx b/src/components/panes/PaneContainer.tsx
index 79897f601..67c6ad00d 100644
--- a/src/components/panes/PaneContainer.tsx
+++ b/src/components/panes/PaneContainer.tsx
@@ -621,6 +621,14 @@ function PickerWrapper({
? settings?.codingCli?.providers?.[freshAgentType.runtimeProvider]?.model ?? freshAgentType.defaultModel
: freshAgentType.defaultModel
const model = normalizeFreshAgentModel(freshAgentType.sessionType, freshAgentType.runtimeProvider, configuredModel) ?? configuredModel
+ const permissionMode = freshAgentType.settingsVisibility.permissionMode === false
+ ? undefined
+ : providerSettings?.defaultPermissionMode
+ ?? (freshAgentType.runtimeProvider === 'codex'
+ ? settings?.codingCli?.providers?.[freshAgentType.runtimeProvider]?.permissionMode
+ : undefined)
+ ?? providerConfig?.defaultPermissionMode
+ ?? freshAgentType.defaultPermissionMode
return {
kind: 'fresh-agent',
sessionType: freshAgentType.sessionType,
@@ -629,12 +637,7 @@ function PickerWrapper({
status: 'creating',
modelSelection: normalizeAgentChatModelSelection(providerSettings?.modelSelection),
model,
- permissionMode: providerSettings?.defaultPermissionMode
- ?? (freshAgentType.runtimeProvider === 'codex'
- ? settings?.codingCli?.providers?.[freshAgentType.runtimeProvider]?.permissionMode
- : undefined)
- ?? providerConfig?.defaultPermissionMode
- ?? freshAgentType.defaultPermissionMode,
+ ...(permissionMode ? { permissionMode } : {}),
sandbox: freshAgentType.runtimeProvider === 'codex'
? settings?.codingCli?.providers?.[freshAgentType.runtimeProvider]?.sandbox
: undefined,
diff --git a/src/lib/fresh-agent-registry.ts b/src/lib/fresh-agent-registry.ts
index 176dd548b..f79b42918 100644
--- a/src/lib/fresh-agent-registry.ts
+++ b/src/lib/fresh-agent-registry.ts
@@ -117,11 +117,11 @@ export const FRESH_AGENT_REGISTRY: readonly FreshAgentRegistryEntry[] = [
label: 'Freshopencode',
icon: OpencodeIcon,
defaultModel: FRESHOPENCODE_DEFAULT_MODEL,
- defaultPermissionMode: 'bypassPermissions',
+ defaultPermissionMode: 'default',
defaultEffort: FRESHOPENCODE_DEFAULT_EFFORT,
settingsVisibility: {
model: true,
- permissionMode: true,
+ permissionMode: false,
effort: true,
thinking: true,
tools: true,
diff --git a/src/lib/session-type-utils.ts b/src/lib/session-type-utils.ts
index 74b3b15d6..79c83373e 100644
--- a/src/lib/session-type-utils.ts
+++ b/src/lib/session-type-utils.ts
@@ -65,6 +65,9 @@ export function buildResumeContent(opts: {
if (freshAgentType) {
const agentConfig = getAgentChatProviderConfig(opts.sessionType)
const ps = opts.agentChatProviderSettings
+ const permissionMode = freshAgentType.settingsVisibility.permissionMode === false
+ ? undefined
+ : ps?.defaultPermissionMode ?? agentConfig?.defaultPermissionMode ?? freshAgentType.defaultPermissionMode
return {
kind: 'fresh-agent',
sessionType: freshAgentType.sessionType,
@@ -77,7 +80,7 @@ export function buildResumeContent(opts: {
initialCwd: opts.cwd,
modelSelection: ps?.modelSelection,
model: freshAgentType.defaultModel,
- permissionMode: ps?.defaultPermissionMode ?? agentConfig?.defaultPermissionMode ?? freshAgentType.defaultPermissionMode,
+ ...(permissionMode ? { permissionMode } : {}),
effort: ps?.effort,
}
}
diff --git a/test/integration/extension-system.test.ts b/test/integration/extension-system.test.ts
index 44854ce56..d98452a36 100644
--- a/test/integration/extension-system.test.ts
+++ b/test/integration/extension-system.test.ts
@@ -211,4 +211,18 @@ describe('Extension system integration', () => {
scrollInputPolicy: 'fallbackToCursorKeysWhenAltScreenMouseCapture',
})
})
+
+ it('builtin OpenCode extension does not expose Freshell permission controls', () => {
+ const builtinDir = path.resolve(process.cwd(), 'extensions')
+ mgr.scan([builtinDir])
+
+ const opencode = mgr.toClientRegistry().find((entry) => entry.name === 'opencode')
+
+ expect(opencode).toBeDefined()
+ expect(opencode?.label).toBe('OpenCode')
+ expect(opencode?.category).toBe('cli')
+ expect(opencode?.cli?.supportsResume).toBe(true)
+ expect(opencode?.cli?.supportsModel).toBe(true)
+ expect(opencode?.cli?.supportsPermissionMode).toBeFalsy()
+ })
})
diff --git a/test/unit/client/components/fresh-agent/FreshAgentView.test.tsx b/test/unit/client/components/fresh-agent/FreshAgentView.test.tsx
index 91bbbd9cb..15d36529a 100644
--- a/test/unit/client/components/fresh-agent/FreshAgentView.test.tsx
+++ b/test/unit/client/components/fresh-agent/FreshAgentView.test.tsx
@@ -410,6 +410,87 @@ describe('FreshAgentView', () => {
expect(screen.queryByRole('button', { name: 'Fork' })).not.toBeInTheDocument()
})
+ it('does not transmit stale Freshopencode permissionMode on create or send', async () => {
+ const creatingStore = createStore()
+ creatingStore.dispatch(initLayout({
+ tabId: 'tab-1',
+ paneId: 'pane-1',
+ content: {
+ kind: 'fresh-agent',
+ sessionType: 'freshopencode',
+ provider: 'opencode',
+ createRequestId: 'req-opencode-policy',
+ status: 'creating',
+ initialCwd: '/repo',
+ model: 'opencode-go/deepseek-v4-flash',
+ effort: 'max',
+ permissionMode: 'bypassPermissions',
+ },
+ }))
+
+ render(
+
+
+ ,
+ )
+
+ const createMessage = wsMock.send.mock.calls
+ .map(([message]) => message)
+ .find((message) => message?.type === 'freshAgent.create')
+ expect(createMessage).toBeDefined()
+ expect(createMessage).not.toHaveProperty('permissionMode')
+
+ cleanup()
+ wsMock.send.mockClear()
+
+ const sendingStore = createStore()
+ sendingStore.dispatch(initLayout({
+ tabId: 'tab-1',
+ paneId: 'pane-1',
+ content: {
+ kind: 'fresh-agent',
+ sessionType: 'freshopencode',
+ provider: 'opencode',
+ createRequestId: 'req-opencode-send-policy',
+ sessionId: 'freshopencode-req-opencode-send-policy',
+ status: 'idle',
+ initialCwd: '/repo',
+ model: 'opencode-go/deepseek-v4-flash',
+ effort: 'max',
+ permissionMode: 'bypassPermissions',
+ },
+ }))
+
+ render(
+
+
+ ,
+ )
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name: 'Chat message input' })).not.toBeDisabled()
+ })
+ wsMock.send.mockClear()
+
+ fireEvent.change(screen.getByRole('textbox', { name: 'Chat message input' }), {
+ target: { value: 'Use local OpenCode policy' },
+ })
+ fireEvent.click(screen.getByRole('button', { name: 'Send' }))
+
+ expect(wsMock.send).toHaveBeenCalledWith({
+ type: 'freshAgent.send',
+ sessionId: 'freshopencode-req-opencode-send-policy',
+ sessionType: 'freshopencode',
+ provider: 'opencode',
+ text: 'Use local OpenCode policy',
+ settings: {
+ cwd: '/repo',
+ model: 'opencode-go/deepseek-v4-flash',
+ effort: 'max',
+ },
+ })
+ })
+
it('auto-titles the fresh-agent pane and tab from the first user message', async () => {
const store = createStore()
store.dispatch(initLayout({
diff --git a/test/unit/client/components/panes/PaneContainer.test.tsx b/test/unit/client/components/panes/PaneContainer.test.tsx
index 7c9c417f3..f91d08924 100644
--- a/test/unit/client/components/panes/PaneContainer.test.tsx
+++ b/test/unit/client/components/panes/PaneContainer.test.tsx
@@ -1822,6 +1822,7 @@ describe('PaneContainer', () => {
expect(paneContent.initialCwd).toBe('/home/user/freshopencode-project')
expect(paneContent.model).toBe('opencode-go/deepseek-v4-flash')
expect(paneContent.effort).toBe('max')
+ expect(paneContent.permissionMode).toBeUndefined()
}
})
diff --git a/test/unit/client/lib/session-type-utils.test.ts b/test/unit/client/lib/session-type-utils.test.ts
index 68ae929e4..e1488223a 100644
--- a/test/unit/client/lib/session-type-utils.test.ts
+++ b/test/unit/client/lib/session-type-utils.test.ts
@@ -62,6 +62,29 @@ describe('buildResumeContent', () => {
expect(content.effort).toBeUndefined()
})
+ it('returns freshopencode resume content without a Freshell permission mode', () => {
+ const content = buildResumeContent({
+ sessionType: 'freshopencode',
+ sessionId: 'ses_opencode_123',
+ cwd: '/home/user/project',
+ agentChatProviderSettings: {
+ defaultPermissionMode: 'bypassPermissions',
+ },
+ })
+
+ expect(content.kind).toBe('fresh-agent')
+ if (content.kind !== 'fresh-agent') throw new Error('expected fresh-agent')
+ expect(content.sessionType).toBe('freshopencode')
+ expect(content.provider).toBe('opencode')
+ expect(content.resumeSessionId).toBe('ses_opencode_123')
+ expect(content.sessionRef).toEqual({
+ provider: 'opencode',
+ sessionId: 'ses_opencode_123',
+ })
+ expect(content.initialCwd).toBe('/home/user/project')
+ expect(content.permissionMode).toBeUndefined()
+ })
+
it('returns fresh-agent content for kilroy sessionType', () => {
const content = buildResumeContent({
sessionType: 'kilroy',
diff --git a/test/unit/server/extension-manager.test.ts b/test/unit/server/extension-manager.test.ts
index cf6bc5e92..ada133893 100644
--- a/test/unit/server/extension-manager.test.ts
+++ b/test/unit/server/extension-manager.test.ts
@@ -411,7 +411,7 @@ describe('ExtensionManager', () => {
command: 'opencode',
resumeArgs: ['--session', '{{sessionId}}'],
modelArgs: ['--model', '{{model}}'],
- permissionModeEnvVar: 'OPENCODE_PERMISSION',
+ permissionModeEnvVar: 'AGENT_PERMISSION_MODE',
permissionModeValues: {
plan: '{"edit":"ask","bash":"ask"}',
},
diff --git a/test/unit/server/extension-manifest.test.ts b/test/unit/server/extension-manifest.test.ts
index aa8bc8cdd..a847094cb 100644
--- a/test/unit/server/extension-manifest.test.ts
+++ b/test/unit/server/extension-manifest.test.ts
@@ -83,7 +83,7 @@ describe('ExtensionManifestSchema', () => {
modelArgs: ['--model', '{{model}}'],
sandboxArgs: ['--sandbox', '{{sandbox}}'],
permissionModeArgs: ['--permission-mode', '{{permissionMode}}'],
- permissionModeEnvVar: 'OPENCODE_PERMISSION',
+ permissionModeEnvVar: 'AGENT_PERMISSION_MODE',
permissionModeValues: {
plan: '{"edit":"ask","bash":"ask"}',
},
diff --git a/test/unit/server/fresh-agent/opencode-adapter.test.ts b/test/unit/server/fresh-agent/opencode-adapter.test.ts
index 750b2fc25..718479682 100644
--- a/test/unit/server/fresh-agent/opencode-adapter.test.ts
+++ b/test/unit/server/fresh-agent/opencode-adapter.test.ts
@@ -66,7 +66,7 @@ const exportedSession = {
describe('OpenCode fresh-agent adapter', () => {
it('creates a placeholder and materializes it on first send with model and effort', async () => {
const { spawnFn, calls } = makeSpawn({
- 'run reply ok --format json --dangerously-skip-permissions --model opencode-go/deepseek-v4-flash --variant max': {
+ 'run reply ok --format json --model opencode-go/deepseek-v4-flash --variant max': {
stdout: '{"type":"step_start","sessionID":"ses_real_1"}\n{"type":"text","part":{"text":"ok"}}\n',
},
'export ses_real_1': {
@@ -96,7 +96,6 @@ describe('OpenCode fresh-agent adapter', () => {
'reply ok',
'--format',
'json',
- '--dangerously-skip-permissions',
'--model',
'opencode-go/deepseek-v4-flash',
'--variant',
@@ -123,10 +122,10 @@ describe('OpenCode fresh-agent adapter', () => {
it('continues a materialized session on later sends', async () => {
const { spawnFn, calls } = makeSpawn({
- 'run first --format json --dangerously-skip-permissions --model opencode-go/glm-5.1 --variant high': {
+ 'run first --format json --model opencode-go/glm-5.1 --variant high': {
stdout: '{"type":"step_start","sessionID":"ses_real_2"}\n',
},
- 'run second --format json --dangerously-skip-permissions --session ses_real_2 --model opencode-go/glm-5.1 --variant high': {
+ 'run second --format json --session ses_real_2 --model opencode-go/glm-5.1 --variant high': {
stdout: '{"type":"step_start","sessionID":"ses_real_2"}\n',
},
})
@@ -152,10 +151,10 @@ describe('OpenCode fresh-agent adapter', () => {
info: { ...exportedSession.info, id: 'ses_restored_1' },
}
const { spawnFn, calls } = makeSpawn({
- 'run reply ok --format json --dangerously-skip-permissions --session ses_restored_1': {
+ 'run reply ok --format json --session ses_restored_1': {
stdout: '{"type":"step_start","sessionID":"ses_restored_1"}\n',
},
- 'run /compact keep decisions --format json --dangerously-skip-permissions --session ses_restored_1': {
+ 'run /compact keep decisions --format json --session ses_restored_1': {
stdout: '{"type":"step_start","sessionID":"ses_restored_1"}\n',
},
'export ses_restored_1': {
@@ -177,7 +176,6 @@ describe('OpenCode fresh-agent adapter', () => {
'reply ok',
'--format',
'json',
- '--dangerously-skip-permissions',
'--session',
'ses_restored_1',
])
@@ -186,7 +184,6 @@ describe('OpenCode fresh-agent adapter', () => {
'/compact keep decisions',
'--format',
'json',
- '--dangerously-skip-permissions',
'--session',
'ses_restored_1',
])
@@ -207,7 +204,7 @@ describe('OpenCode fresh-agent adapter', () => {
it('accepts partial per-turn settings from the client send path', async () => {
const { spawnFn, calls } = makeSpawn({
- 'run reply ok --format json --dangerously-skip-permissions --model opencode-go/deepseek-v4-flash --variant high': {
+ 'run reply ok --format json --model opencode-go/deepseek-v4-flash --variant high': {
stdout: '{"type":"step_start","sessionID":"ses_real_3"}\n',
},
})
@@ -233,7 +230,6 @@ describe('OpenCode fresh-agent adapter', () => {
'reply ok',
'--format',
'json',
- '--dangerously-skip-permissions',
'--model',
'opencode-go/deepseek-v4-flash',
'--variant',
diff --git a/test/unit/server/terminal-registry.test.ts b/test/unit/server/terminal-registry.test.ts
index 470d5745d..78eb81029 100644
--- a/test/unit/server/terminal-registry.test.ts
+++ b/test/unit/server/terminal-registry.test.ts
@@ -1059,26 +1059,36 @@ describe('buildSpawnSpec Unix paths', () => {
expect(spec.env.GOOGLE_GENERATIVE_AI_API_KEY).toBe('gemini-key')
})
- it('maps OpenCode plan permission mode to OPENCODE_PERMISSION env', () => {
+ it('does not set OPENCODE_PERMISSION for OpenCode when permission mode is provided', () => {
delete process.env.OPENCODE_CMD
const spec = buildSpawnSpec('opencode', '/Users/john/project', 'system', undefined, {
- permissionMode: 'plan',
+ permissionMode: 'bypassPermissions',
opencodeServer: TEST_OPENCODE_SERVER,
})
- expect(spec.env.OPENCODE_PERMISSION).toBe('{"edit":"ask","bash":"ask"}')
+ expect(spec.env).not.toHaveProperty('OPENCODE_PERMISSION')
+ expect(spec.args).not.toContain('--permission-mode')
+ expect(spec.args).toContain('--hostname')
+ expect(spec.args).toContain('127.0.0.1')
+ expect(spec.args).toContain('--port')
+ expect(spec.args).toContain(String(TEST_OPENCODE_SERVER.port))
})
- it('maps OpenCode acceptEdits permission mode to OPENCODE_PERMISSION env', () => {
+ it('keeps OpenCode model and resume behavior while ignoring permission mode', () => {
delete process.env.OPENCODE_CMD
- const spec = buildSpawnSpec('opencode', '/Users/john/project', 'system', undefined, {
- permissionMode: 'acceptEdits',
+ const spec = buildSpawnSpec('opencode', '/Users/john/project', 'system', 'ses_existing', {
+ permissionMode: 'plan',
+ model: 'openai/gpt-5-mini',
opencodeServer: TEST_OPENCODE_SERVER,
})
- expect(spec.env.OPENCODE_PERMISSION).toBe('{"edit":"allow","bash":"ask"}')
+ expect(spec.env).not.toHaveProperty('OPENCODE_PERMISSION')
+ expect(spec.args).toContain('--session')
+ expect(spec.args).toContain('ses_existing')
+ expect(spec.args).not.toContain('--model')
+ expect(spec.args).not.toContain('openai/gpt-5-mini')
})
it('scrubs inherited OpenCode server auth env for managed TUI endpoints', () => {