diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9eed38c..88496a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 All notable changes to this project will be documented in this file.
 
+## [Unreleased]
+
+### Added
+
+- **Upstream `OPENCODE_SERVER_PASSWORD` support** (closes #39): When `OPENCODE_SERVER_PASSWORD` (and optionally `OPENCODE_SERVER_USERNAME`) is set in the bot's environment, the credentials are automatically forwarded as HTTP Basic auth on every internal request to the local `opencode serve` process — session HTTP calls, the SSE `/event` stream, and readiness probes. Behavior is unchanged when the env vars are not set. Misconfigured credentials surface a clear actionable error instead of a vague connection failure. This is optional hardening / compatibility with upstream opencode auth, not a replacement for the Discord allowlist.
+
 ## [1.5.2] - 2026-04-05
 
 ### Added
diff --git a/README.md b/README.md
index 1545cac..040c6be 100644
--- a/README.md
+++ b/README.md
@@ -175,7 +175,7 @@ If you prefer manual setup or need to troubleshoot:
 | `remote-opencode setup`                 | Interactive setup wizard — configures bot token, IDs |
 | `remote-opencode start`                 | Start the Discord bot                                |
 | `remote-opencode deploy`                | Deploy/update slash commands to Discord              |
-| `remote-opencode undeploy`              | Remove slash commands from Discord                  |
+| `remote-opencode undeploy`              | Remove slash commands from Discord                   |
 | `remote-opencode config`                | Display current configuration info                   |
 | `remote-opencode allow add <userId>`    | Add a Discord user ID to the allowlist               |
 | `remote-opencode allow remove <userId>` | Remove a Discord user ID from the allowlist          |
@@ -357,9 +357,9 @@ Show git diffs for the current project directly in Discord — perfect for revie
 
 | Parameter | Description                                                        |
 | --------- | ------------------------------------------------------------------ |
-| `target`  | `unstaged` (default), `staged`, or `branch`                       |
+| `target`  | `unstaged` (default), `staged`, or `branch`                        |
 | `stat`    | Show `--stat` summary only instead of full diff (default: `false`) |
-| `base`    | Base branch for `target:branch` diff (default: `main`)            |
+| `base`    | Base branch for `target:branch` diff (default: `main`)             |
 
 **How it works:**
 
@@ -411,9 +411,9 @@ Manage voice message transcription settings. Requires an OpenAI API key (set via
 /voice remove               Remove the stored OpenAI API key
 ```
 
-| Parameter | Description |
-| --------- | ----------------------------------------- |
-| (none) | Subcommands only: `status`, `remove` |
+| Parameter | Description                          |
+| --------- | ------------------------------------ |
+| (none)    | Subcommands only: `status`, `remove` |
 
 **How it works:**
 
@@ -436,10 +436,10 @@ View available AI models or set the model for the current channel.
 /model set name:anthropic/claude-sonnet-4-20250514
 ```
 
-| Subcommand | Description                                       |
-| ---------- | ------------------------------------------------- |
-| `list`     | Show all available models grouped by provider     |
-| `set`      | Set the AI model for the current channel/thread   |
+| Subcommand | Description                                     |
+| ---------- | ----------------------------------------------- |
+| `list`     | Show all available models grouped by provider   |
+| `set`      | Set the AI model for the current channel/thread |
 
 **Features:**
 
@@ -612,6 +612,31 @@ remote-opencode allow reset    # Clears entire allowlist (disables access contro
 - **Discord `/allow` is disabled when allowlist is empty** — prevents bootstrap attacks
 - **Config file permissions** are set to `0o600` (owner-read/write only)
 
+### OpenCode server password (optional)
+
+`remote-opencode` communicates with a local `opencode serve` process bound to
+`127.0.0.1`. If you already run `opencode serve` with upstream HTTP Basic auth
+enabled via `OPENCODE_SERVER_PASSWORD` (and optionally `OPENCODE_SERVER_USERNAME`),
+the bot will automatically pick up the same credentials from its own environment
+and apply them to all internal communication — session HTTP calls, the SSE
+`/event` stream, and readiness probes.
+
+```bash
+# Example: start the bot with upstream opencode auth enabled
+OPENCODE_SERVER_PASSWORD='your-password' remote-opencode start
+```
+
+Notes:
+
+- Behavior is **unchanged when these env vars are not set** — this is purely
+  optional hardening / compatibility for users who already rely on upstream
+  `opencode serve` auth.
+- This is **not a replacement** for the Discord allowlist described above; it is
+  an additional layer for the local HTTP surface only.
+- `OPENCODE_SERVER_USERNAME` defaults to `opencode` to match upstream.
+- Misconfiguration produces a clear error (e.g. `opencode server rejected
+credentials (HTTP 401) ...`) rather than a vague connection failure.
+
 ---
 
 ## Configuration
diff --git a/src/__tests__/serveManager.test.ts b/src/__tests__/serveManager.test.ts
index 6d42b7f..e29e17e 100644
--- a/src/__tests__/serveManager.test.ts
+++ b/src/__tests__/serveManager.test.ts
@@ -1,18 +1,18 @@
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
-import { EventEmitter } from 'node:events';
-import type { ChildProcess } from 'node:child_process';
-import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
-import { tmpdir } from 'node:os';
-import { join } from 'node:path';
-
-vi.mock('node:child_process', () => ({
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import { EventEmitter } from "node:events";
+import type { ChildProcess } from "node:child_process";
+import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+
+vi.mock("node:child_process", () => ({
   spawn: vi.fn(),
 }));
 
-vi.mock('node:net', () => ({
+vi.mock("node:net", () => ({
   Server: class MockServer extends EventEmitter {
     listen(port: number, callback?: () => void) {
-      setImmediate(() => this.emit('listening'));
+      setImmediate(() => this.emit("listening"));
       return this;
     }
     close(callback?: () => void) {
@@ -24,17 +24,17 @@ vi.mock('node:net', () => ({
   },
 }));
 
-vi.mock('../services/configStore.js', () => ({
+vi.mock("../services/configStore.js", () => ({
   getPortConfig: vi.fn(),
 }));
 
-import * as serveManager from '../services/serveManager.js';
-import { getPortConfig } from '../services/configStore.js';
-import { spawn } from 'node:child_process';
+import * as serveManager from "../services/serveManager.js";
+import { getPortConfig } from "../services/configStore.js";
+import { spawn } from "node:child_process";
 
 const createMockProcess = (): ChildProcess => {
   const proc = new EventEmitter() as ChildProcess;
-  Object.defineProperty(proc, 'pid', {
+  Object.defineProperty(proc, "pid", {
     value: Math.floor(Math.random() * 10000),
     writable: true,
   });
@@ -44,13 +44,13 @@ const createMockProcess = (): ChildProcess => {
   return proc;
 };
 
-describe('serveManager', () => {
+describe("serveManager", () => {
   let originalPath: string | undefined;
 
   beforeEach(() => {
     vi.resetAllMocks();
     originalPath = process.env.PATH;
-    vi.stubEnv('PATH', '/nonexistent');
+    vi.stubEnv("PATH", "/nonexistent");
   });
 
   afterEach(() => {
@@ -59,53 +59,54 @@ describe('serveManager', () => {
     process.env.PATH = originalPath;
   });
 
-  describe('spawnServe', () => {
-    it('should spawn opencode serve and return port', async () => {
+  describe("spawnServe", () => {
+    it("should spawn opencode serve and return port", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       const port = await serveManager.spawnServe(projectPath);
 
       expect(port).toBeGreaterThanOrEqual(14097);
       expect(port).toBeLessThanOrEqual(14200);
       expect(spawn).toHaveBeenCalledWith(
-        'opencode',
-        ['serve', '--port', port.toString()],
+        "opencode",
+        ["serve", "--port", port.toString()],
         expect.objectContaining({
           cwd: projectPath,
-          stdio: ['inherit', 'pipe', 'pipe'],
-        })
+          stdio: ["inherit", "pipe", "pipe"],
+        }),
       );
 
       const spawnOptions = vi.mocked(spawn).mock.calls[0]?.[2];
-      expect(spawnOptions).not.toHaveProperty('shell');
+      expect(spawnOptions).not.toHaveProperty("shell");
     });
 
-    it('should resolve opencode from PATH before spawning', async () => {
-      const tempDir = mkdtempSync(join(tmpdir(), 'remote-opencode-'));
-      const executableName = process.platform === 'win32' ? 'opencode.cmd' : 'opencode';
+    it("should resolve opencode from PATH before spawning", async () => {
+      const tempDir = mkdtempSync(join(tmpdir(), "remote-opencode-"));
+      const executableName =
+        process.platform === "win32" ? "opencode.cmd" : "opencode";
       const resolvedPath = join(tempDir, executableName);
 
-      writeFileSync(resolvedPath, '@echo off');
-      vi.stubEnv('PATH', tempDir);
+      writeFileSync(resolvedPath, "@echo off");
+      vi.stubEnv("PATH", tempDir);
 
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
       try {
-        await serveManager.spawnServe('/test/project');
+        await serveManager.spawnServe("/test/project");
         expect(vi.mocked(spawn).mock.calls[0]?.[0]).toBe(resolvedPath);
       } finally {
         rmSync(tempDir, { recursive: true, force: true });
       }
     });
 
-    it('should return existing port if serve already running for project', async () => {
+    it("should return existing port if serve already running for project", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       const port1 = await serveManager.spawnServe(projectPath);
       const port2 = await serveManager.spawnServe(projectPath);
 
@@ -113,43 +114,43 @@ describe('serveManager', () => {
       expect(spawn).toHaveBeenCalledTimes(1);
     });
 
-    it('should allocate different ports for different projects', async () => {
+    it("should allocate different ports for different projects", async () => {
       vi.mocked(spawn).mockImplementation(() => createMockProcess());
 
-      const port1 = await serveManager.spawnServe('/project1');
-      const port2 = await serveManager.spawnServe('/project2');
+      const port1 = await serveManager.spawnServe("/project1");
+      const port2 = await serveManager.spawnServe("/project2");
 
       expect(port1).not.toBe(port2);
       expect(spawn).toHaveBeenCalledTimes(2);
     });
 
-    it('should respect custom port range from config', async () => {
+    it("should respect custom port range from config", async () => {
       vi.mocked(spawn).mockImplementation(() => createMockProcess());
       vi.mocked(getPortConfig).mockReturnValue({ min: 20000, max: 20010 });
 
-      const port = await serveManager.spawnServe('/test/custom-port');
+      const port = await serveManager.spawnServe("/test/custom-port");
 
       expect(port).toBe(20000);
       expect(spawn).toHaveBeenCalledWith(
-        'opencode',
-        ['serve', '--port', '20000'],
-        expect.anything()
+        "opencode",
+        ["serve", "--port", "20000"],
+        expect.anything(),
       );
     });
 
-    it('should clean up when process exits', async () => {
+    it("should clean up when process exits", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       await serveManager.spawnServe(projectPath);
 
       expect(serveManager.getPort(projectPath)).toBeDefined();
 
-      mockProc.emit('exit', 0, null);
+      mockProc.emit("exit", 0, null);
 
       // Wait for async exit handler
-      await new Promise(resolve => setTimeout(resolve, 10));
+      await new Promise((resolve) => setTimeout(resolve, 10));
 
       // Instance should still exist but be marked as exited
       const state = serveManager.getInstanceState(projectPath);
@@ -157,71 +158,76 @@ describe('serveManager', () => {
       expect(state?.exitCode).toBe(0);
     });
 
-    it('should track error message when process exits with non-zero code', async () => {
+    it("should track error message when process exits with non-zero code", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       await serveManager.spawnServe(projectPath);
 
       // Simulate stderr output before exit
-      mockProc.stderr?.emit('data', Buffer.from('Error: opencode command not found'));
-      mockProc.emit('exit', 1, null);
+      mockProc.stderr?.emit(
+        "data",
+        Buffer.from("Error: opencode command not found"),
+      );
+      mockProc.emit("exit", 1, null);
 
-      await new Promise(resolve => setTimeout(resolve, 10));
+      await new Promise((resolve) => setTimeout(resolve, 10));
 
       const state = serveManager.getInstanceState(projectPath);
       expect(state?.exited).toBe(true);
       expect(state?.exitCode).toBe(1);
-      expect(state?.exitError).toContain('opencode command not found');
+      expect(state?.exitError).toContain("opencode command not found");
     });
 
-    it('should track error message when process fails to spawn', async () => {
+    it("should track error message when process fails to spawn", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
       const projectPath = process.cwd();
       await serveManager.spawnServe(projectPath);
 
-      const error = new Error('spawn opencode ENOENT') as NodeJS.ErrnoException;
-      error.code = 'ENOENT';
-      mockProc.emit('error', error);
+      const error = new Error("spawn opencode ENOENT") as NodeJS.ErrnoException;
+      error.code = "ENOENT";
+      mockProc.emit("error", error);
 
-      await new Promise(resolve => setTimeout(resolve, 10));
+      await new Promise((resolve) => setTimeout(resolve, 10));
 
       const state = serveManager.getInstanceState(projectPath);
       expect(state?.exited).toBe(true);
-      expect(state?.exitError).toContain('OpenCode executable not found');
+      expect(state?.exitError).toContain("OpenCode executable not found");
     });
 
-    it('should report missing project path when spawn fails with inaccessible cwd', async () => {
+    it("should report missing project path when spawn fails with inaccessible cwd", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/definitely/missing/project-path';
+      const projectPath = "/definitely/missing/project-path";
       await serveManager.spawnServe(projectPath);
 
-      const error = new Error('spawn opencode ENOENT') as NodeJS.ErrnoException;
-      error.code = 'ENOENT';
-      mockProc.emit('error', error);
+      const error = new Error("spawn opencode ENOENT") as NodeJS.ErrnoException;
+      error.code = "ENOENT";
+      mockProc.emit("error", error);
 
-      await new Promise(resolve => setTimeout(resolve, 10));
+      await new Promise((resolve) => setTimeout(resolve, 10));
 
       const state = serveManager.getInstanceState(projectPath);
       expect(state?.exited).toBe(true);
-      expect(state?.exitError).toContain(`Project path does not exist or is not accessible: ${projectPath}`);
+      expect(state?.exitError).toContain(
+        `Project path does not exist or is not accessible: ${projectPath}`,
+      );
     });
 
-    it('should allow respawning after process exits', async () => {
+    it("should allow respawning after process exits", async () => {
       vi.mocked(spawn).mockImplementation(() => createMockProcess());
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       const port1 = await serveManager.spawnServe(projectPath);
 
       // Get the mock process and mark it as exited
       const mockProc1 = vi.mocked(spawn).mock.results[0].value;
-      mockProc1.emit('exit', 1, null);
-      await new Promise(resolve => setTimeout(resolve, 10));
+      mockProc1.emit("exit", 1, null);
+      await new Promise((resolve) => setTimeout(resolve, 10));
 
       // Should spawn a new process
       const port2 = await serveManager.spawnServe(projectPath);
@@ -232,30 +238,30 @@ describe('serveManager', () => {
     });
   });
 
-  describe('getPort', () => {
-    it('should return port for running serve', async () => {
+  describe("getPort", () => {
+    it("should return port for running serve", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       const expectedPort = await serveManager.spawnServe(projectPath);
 
       const port = serveManager.getPort(projectPath);
       expect(port).toBe(expectedPort);
     });
 
-    it('should return undefined for non-existent serve', () => {
-      const port = serveManager.getPort('/non/existent');
+    it("should return undefined for non-existent serve", () => {
+      const port = serveManager.getPort("/non/existent");
       expect(port).toBeUndefined();
     });
   });
 
-  describe('stopServe', () => {
-    it('should stop serve and return true', async () => {
+  describe("stopServe", () => {
+    it("should stop serve and return true", async () => {
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/project';
+      const projectPath = "/test/project";
       await serveManager.spawnServe(projectPath);
 
       const result = serveManager.stopServe(projectPath);
@@ -265,39 +271,39 @@ describe('serveManager', () => {
       expect(serveManager.getPort(projectPath)).toBeUndefined();
     });
 
-    it('should return false for non-existent serve', () => {
-      const result = serveManager.stopServe('/non/existent');
+    it("should return false for non-existent serve", () => {
+      const result = serveManager.stopServe("/non/existent");
       expect(result).toBe(false);
     });
   });
 
-  describe('stopAll', () => {
-    it('should stop all serve instances', async () => {
+  describe("stopAll", () => {
+    it("should stop all serve instances", async () => {
       const mockProc1 = createMockProcess();
       const mockProc2 = createMockProcess();
       vi.mocked(spawn)
         .mockReturnValueOnce(mockProc1)
         .mockReturnValueOnce(mockProc2);
 
-      await serveManager.spawnServe('/project1');
-      await serveManager.spawnServe('/project2');
+      await serveManager.spawnServe("/project1");
+      await serveManager.spawnServe("/project2");
 
       serveManager.stopAll();
 
       expect(mockProc1.kill).toHaveBeenCalled();
       expect(mockProc2.kill).toHaveBeenCalled();
-      expect(serveManager.getPort('/project1')).toBeUndefined();
-      expect(serveManager.getPort('/project2')).toBeUndefined();
+      expect(serveManager.getPort("/project1")).toBeUndefined();
+      expect(serveManager.getPort("/project2")).toBeUndefined();
     });
 
-    it('should handle empty instances gracefully', () => {
+    it("should handle empty instances gracefully", () => {
       expect(() => serveManager.stopAll()).not.toThrow();
     });
   });
 
-  describe('waitForReady', () => {
+  describe("waitForReady", () => {
     beforeEach(() => {
-      vi.stubGlobal('fetch', vi.fn());
+      vi.stubGlobal("fetch", vi.fn());
       vi.useFakeTimers();
     });
 
@@ -306,20 +312,22 @@ describe('serveManager', () => {
       vi.useRealTimers();
     });
 
-    it('should resolve when fetch returns ok', async () => {
+    it("should resolve when fetch returns ok", async () => {
       vi.mocked(fetch).mockResolvedValue({ ok: true } as Response);
 
       const promise = serveManager.waitForReady(14097);
-      
+
       await vi.runAllTimersAsync();
-      
+
       await expect(promise).resolves.toBeUndefined();
-      expect(fetch).toHaveBeenCalledWith('http://127.0.0.1:14097/session');
+      expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:14097/session", {
+        headers: {},
+      });
     });
 
-    it('should retry if fetch fails or returns not ok', async () => {
+    it("should retry if fetch fails or returns not ok", async () => {
       vi.mocked(fetch)
-        .mockRejectedValueOnce(new Error('Connection refused'))
+        .mockRejectedValueOnce(new Error("Connection refused"))
         .mockResolvedValueOnce({ ok: false } as Response)
         .mockResolvedValueOnce({ ok: true } as Response);
 
@@ -333,54 +341,116 @@ describe('serveManager', () => {
       expect(fetch).toHaveBeenCalledTimes(3);
     });
 
-    it('should throw error on timeout', async () => {
-      vi.mocked(fetch).mockRejectedValue(new Error('Connection refused'));
+    it("should throw error on timeout", async () => {
+      vi.mocked(fetch).mockRejectedValue(new Error("Connection refused"));
 
       const promise = serveManager.waitForReady(14097, 1000);
-      
-      const wrappedPromise = expect(promise).rejects.toThrow('Service at port 14097 failed to become ready within 1000ms. Check if \'opencode serve\' is working correctly.');
+
+      const wrappedPromise = expect(promise).rejects.toThrow(
+        "Service at port 14097 failed to become ready within 1000ms. Check if 'opencode serve' is working correctly.",
+      );
 
       await vi.advanceTimersByTimeAsync(1500);
 
       await wrappedPromise;
     });
 
-    it('should fail fast when process exits early with error', async () => {
+    it("should fail fast when process exits early with error", async () => {
       vi.useRealTimers();
-      vi.mocked(fetch).mockRejectedValue(new Error('Connection refused'));
+      vi.mocked(fetch).mockRejectedValue(new Error("Connection refused"));
 
       const mockProc = createMockProcess();
       vi.mocked(spawn).mockReturnValue(mockProc);
 
-      const projectPath = '/test/fast-fail';
+      const projectPath = "/test/fast-fail";
       const port = await serveManager.spawnServe(projectPath);
 
       // Simulate stderr output and immediate exit
-      mockProc.stderr?.emit('data', Buffer.from('Error: Failed to bind to port'));
-      mockProc.emit('exit', 1, null);
+      mockProc.stderr?.emit(
+        "data",
+        Buffer.from("Error: Failed to bind to port"),
+      );
+      mockProc.emit("exit", 1, null);
 
       // Wait for exit handler to process
-      await new Promise(resolve => setTimeout(resolve, 10));
+      await new Promise((resolve) => setTimeout(resolve, 10));
 
       // Now waitForReady should fail fast with the error message
-      await expect(serveManager.waitForReady(port, 30000, projectPath)).rejects.toThrow(
-        'opencode serve failed to start: Error: Failed to bind to port'
+      await expect(
+        serveManager.waitForReady(port, 30000, projectPath),
+      ).rejects.toThrow(
+        "opencode serve failed to start: Error: Failed to bind to port",
       );
 
       vi.useFakeTimers();
     });
 
-    it('should still timeout if no projectPath provided and process exits', async () => {
-      vi.mocked(fetch).mockRejectedValue(new Error('Connection refused'));
+    it("should still timeout if no projectPath provided and process exits", async () => {
+      vi.mocked(fetch).mockRejectedValue(new Error("Connection refused"));
 
       // Without projectPath, can't detect early exit
       const promise = serveManager.waitForReady(14097, 1000);
-      
-      const wrappedPromise = expect(promise).rejects.toThrow('Service at port 14097 failed to become ready within 1000ms');
+
+      const wrappedPromise = expect(promise).rejects.toThrow(
+        "Service at port 14097 failed to become ready within 1000ms",
+      );
 
       await vi.advanceTimersByTimeAsync(1500);
 
       await wrappedPromise;
     });
+
+    describe("OPENCODE_SERVER_PASSWORD auth", () => {
+      const originalPassword = process.env.OPENCODE_SERVER_PASSWORD;
+
+      afterEach(() => {
+        if (originalPassword === undefined)
+          delete process.env.OPENCODE_SERVER_PASSWORD;
+        else process.env.OPENCODE_SERVER_PASSWORD = originalPassword;
+      });
+
+      it("sends Basic Authorization header to readiness probe when password is set", async () => {
+        process.env.OPENCODE_SERVER_PASSWORD = "s3cret";
+        vi.mocked(fetch).mockResolvedValue({ ok: true } as Response);
+
+        const promise = serveManager.waitForReady(14097);
+        await vi.runAllTimersAsync();
+        await expect(promise).resolves.toBeUndefined();
+
+        const expected = `Basic ${Buffer.from("opencode:s3cret").toString("base64")}`;
+        expect(fetch).toHaveBeenCalledWith("http://127.0.0.1:14097/session", {
+          headers: { Authorization: expected },
+        });
+      });
+
+      it("fails fast with a clear error when readiness probe returns 401", async () => {
+        vi.useRealTimers();
+        process.env.OPENCODE_SERVER_PASSWORD = "wrong";
+        vi.mocked(fetch).mockResolvedValue({
+          ok: false,
+          status: 401,
+        } as Response);
+
+        await expect(serveManager.waitForReady(14097, 5000)).rejects.toThrow(
+          /rejected by the opencode server/i,
+        );
+
+        vi.useFakeTimers();
+      });
+
+      it("fails fast with a clear error when readiness probe returns 401 and password is unset", async () => {
+        vi.useRealTimers();
+        vi.mocked(fetch).mockResolvedValue({
+          ok: false,
+          status: 401,
+        } as Response);
+
+        await expect(serveManager.waitForReady(14097, 5000)).rejects.toThrow(
+          /OPENCODE_SERVER_PASSWORD is not set/i,
+        );
+
+        vi.useFakeTimers();
+      });
+    });
   });
 });
diff --git a/src/__tests__/serverAuth.test.ts b/src/__tests__/serverAuth.test.ts
new file mode 100644
index 0000000..6fcae9f
--- /dev/null
+++ b/src/__tests__/serverAuth.test.ts
@@ -0,0 +1,100 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+
+describe("serverAuth", () => {
+  const originalPassword = process.env.OPENCODE_SERVER_PASSWORD;
+  const originalUsername = process.env.OPENCODE_SERVER_USERNAME;
+
+  beforeEach(() => {
+    delete process.env.OPENCODE_SERVER_PASSWORD;
+    delete process.env.OPENCODE_SERVER_USERNAME;
+    vi.resetModules();
+  });
+
+  afterEach(() => {
+    if (originalPassword === undefined)
+      delete process.env.OPENCODE_SERVER_PASSWORD;
+    else process.env.OPENCODE_SERVER_PASSWORD = originalPassword;
+    if (originalUsername === undefined)
+      delete process.env.OPENCODE_SERVER_USERNAME;
+    else process.env.OPENCODE_SERVER_USERNAME = originalUsername;
+  });
+
+  describe("when OPENCODE_SERVER_PASSWORD is not set (current behavior)", () => {
+    it("reports auth disabled", async () => {
+      const mod = await import("../services/serverAuth.js");
+      expect(mod.isAuthEnabled()).toBe(false);
+      expect(mod.getAuthToken()).toBeUndefined();
+      expect(mod.getAuthHeaders()).toEqual({});
+    });
+
+    it("assertNotAuthError is a no-op for non-auth statuses", async () => {
+      const mod = await import("../services/serverAuth.js");
+      expect(() => mod.assertNotAuthError(200, "ctx")).not.toThrow();
+      expect(() => mod.assertNotAuthError(404, "ctx")).not.toThrow();
+      expect(() => mod.assertNotAuthError(500, "ctx")).not.toThrow();
+    });
+
+    it("assertNotAuthError throws a helpful error when server demands auth but env is not set", async () => {
+      const mod = await import("../services/serverAuth.js");
+      expect(() =>
+        mod.assertNotAuthError(401, "Failed to create session"),
+      ).toThrow(/OPENCODE_SERVER_PASSWORD is not set/i);
+    });
+
+    it("treats an empty OPENCODE_SERVER_PASSWORD as disabled", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "";
+      const mod = await import("../services/serverAuth.js");
+      expect(mod.isAuthEnabled()).toBe(false);
+    });
+  });
+
+  describe("when OPENCODE_SERVER_PASSWORD is set", () => {
+    beforeEach(() => {
+      process.env.OPENCODE_SERVER_PASSWORD = "s3cret";
+    });
+
+    it("reports auth enabled and builds a Basic token with the default username", async () => {
+      const mod = await import("../services/serverAuth.js");
+      expect(mod.isAuthEnabled()).toBe(true);
+
+      const expected = Buffer.from("opencode:s3cret").toString("base64");
+      expect(mod.getAuthToken()).toBe(expected);
+      expect(mod.getAuthHeaders()).toEqual({
+        Authorization: `Basic ${expected}`,
+      });
+    });
+
+    it("uses OPENCODE_SERVER_USERNAME when provided", async () => {
+      process.env.OPENCODE_SERVER_USERNAME = "alice";
+      const mod = await import("../services/serverAuth.js");
+
+      const expected = Buffer.from("alice:s3cret").toString("base64");
+      expect(mod.getAuthToken()).toBe(expected);
+      expect(mod.getAuthHeaders()).toEqual({
+        Authorization: `Basic ${expected}`,
+      });
+    });
+
+    it("falls back to the default username when OPENCODE_SERVER_USERNAME is empty", async () => {
+      process.env.OPENCODE_SERVER_USERNAME = "";
+      const mod = await import("../services/serverAuth.js");
+
+      const expected = Buffer.from("opencode:s3cret").toString("base64");
+      expect(mod.getAuthToken()).toBe(expected);
+    });
+
+    it("assertNotAuthError surfaces a credential-mismatch error on 401", async () => {
+      const mod = await import("../services/serverAuth.js");
+      expect(() =>
+        mod.assertNotAuthError(401, "Failed to create session"),
+      ).toThrow(/rejected credentials/i);
+    });
+
+    it("assertNotAuthError also surfaces 403 as a credential error", async () => {
+      const mod = await import("../services/serverAuth.js");
+      expect(() =>
+        mod.assertNotAuthError(403, "Failed to send prompt"),
+      ).toThrow(/rejected credentials/i);
+    });
+  });
+});
diff --git a/src/__tests__/sessionManager.test.ts b/src/__tests__/sessionManager.test.ts
index ce864b3..64e4f6a 100644
--- a/src/__tests__/sessionManager.test.ts
+++ b/src/__tests__/sessionManager.test.ts
@@ -1,13 +1,32 @@
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
 const dataStoreMock = vi.hoisted(() => {
-  const threadSessions = new Map<string, { threadId: string; sessionId: string; projectPath: string; port: number; createdAt: number; lastUsedAt: number }>();
+  const threadSessions = new Map<
+    string,
+    {
+      threadId: string;
+      sessionId: string;
+      projectPath: string;
+      port: number;
+      createdAt: number;
+      lastUsedAt: number;
+    }
+  >();
 
   return {
     reset: () => threadSessions.clear(),
     getThreadSession: vi.fn((threadId: string) => threadSessions.get(threadId)),
-    setThreadSession: vi.fn((session: { threadId: string; sessionId: string; projectPath: string; port: number; createdAt: number; lastUsedAt: number }) => {
-      threadSessions.set(session.threadId, session);
-    }),
+    setThreadSession: vi.fn(
+      (session: {
+        threadId: string;
+        sessionId: string;
+        projectPath: string;
+        port: number;
+        createdAt: number;
+        lastUsedAt: number;
+      }) => {
+        threadSessions.set(session.threadId, session);
+      },
+    ),
     updateThreadSessionLastUsed: vi.fn((threadId: string) => {
       const session = threadSessions.get(threadId);
       if (session) {
@@ -20,7 +39,7 @@ const dataStoreMock = vi.hoisted(() => {
   };
 });
 
-vi.mock('../services/dataStore.js', () => ({
+vi.mock("../services/dataStore.js", () => ({
   getThreadSession: dataStoreMock.getThreadSession,
   setThreadSession: dataStoreMock.setThreadSession,
   updateThreadSessionLastUsed: dataStoreMock.updateThreadSessionLastUsed,
@@ -30,6 +49,10 @@ vi.mock('../services/dataStore.js', () => ({
 import {
   createSession,
   sendPrompt,
+  validateSession,
+  getSessionInfo,
+  listSessions,
+  abortSession,
   ensureSessionForThread,
   getSessionForThread,
   setSessionForThread,
@@ -37,277 +60,472 @@ import {
   setSseClient,
   getSseClient,
   clearSseClient,
-} from '../services/sessionManager.js';
-import { SSEClient } from '../services/sseClient.js';
+} from "../services/sessionManager.js";
+import { SSEClient } from "../services/sseClient.js";
 
-describe('SessionManager', () => {
+describe("SessionManager", () => {
   const mockFetch = vi.fn();
+  const originalPassword = process.env.OPENCODE_SERVER_PASSWORD;
+  const originalUsername = process.env.OPENCODE_SERVER_USERNAME;
 
   beforeEach(() => {
-    vi.stubGlobal('fetch', mockFetch);
+    vi.stubGlobal("fetch", mockFetch);
     mockFetch.mockReset();
     dataStoreMock.reset();
     vi.clearAllMocks();
+    delete process.env.OPENCODE_SERVER_PASSWORD;
+    delete process.env.OPENCODE_SERVER_USERNAME;
   });
 
   afterEach(() => {
     vi.unstubAllGlobals();
+    if (originalPassword === undefined)
+      delete process.env.OPENCODE_SERVER_PASSWORD;
+    else process.env.OPENCODE_SERVER_PASSWORD = originalPassword;
+    if (originalUsername === undefined)
+      delete process.env.OPENCODE_SERVER_USERNAME;
+    else process.env.OPENCODE_SERVER_USERNAME = originalUsername;
   });
 
-  describe('createSession', () => {
-    it('should create a session via HTTP POST and return sessionId', async () => {
-      const mockSessionId = 'ses_abc123';
+  describe("createSession", () => {
+    it("should create a session via HTTP POST and return sessionId", async () => {
+      const mockSessionId = "ses_abc123";
       mockFetch.mockResolvedValueOnce({
         ok: true,
-        json: async () => ({ id: mockSessionId, slug: 'test-session' }),
+        json: async () => ({ id: mockSessionId, slug: "test-session" }),
       });
 
       const sessionId = await createSession(3000);
 
-      expect(mockFetch).toHaveBeenCalledWith('http://127.0.0.1:3000/session', {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: '{}',
+      expect(mockFetch).toHaveBeenCalledWith("http://127.0.0.1:3000/session", {
+        method: "POST",
+        headers: { "Content-Type": "application/json" },
+        body: "{}",
       });
       expect(sessionId).toBe(mockSessionId);
     });
 
-    it('should throw error if HTTP request fails', async () => {
+    it("should throw error if HTTP request fails", async () => {
       mockFetch.mockResolvedValueOnce({
         ok: false,
         status: 500,
-        statusText: 'Internal Server Error',
+        statusText: "Internal Server Error",
       });
 
       await expect(createSession(3000)).rejects.toThrow(
-        'Failed to create session: 500 Internal Server Error'
+        "Failed to create session: 500 Internal Server Error",
       );
     });
 
-    it('should throw error if response is missing id', async () => {
+    it("should throw error if response is missing id", async () => {
       mockFetch.mockResolvedValueOnce({
         ok: true,
-        json: async () => ({ slug: 'test-session' }),
+        json: async () => ({ slug: "test-session" }),
       });
 
       await expect(createSession(3000)).rejects.toThrow(
-        'Invalid session response: missing id'
+        "Invalid session response: missing id",
       );
     });
   });
 
-  describe('sendPrompt', () => {
-    it('should send prompt via HTTP POST with correct payload', async () => {
+  describe("sendPrompt", () => {
+    it("should send prompt via HTTP POST with correct payload", async () => {
       mockFetch.mockResolvedValueOnce({
         ok: true,
         status: 204,
       });
 
-      await sendPrompt(3000, 'ses_abc123', 'Hello OpenCode');
+      await sendPrompt(3000, "ses_abc123", "Hello OpenCode");
 
       expect(mockFetch).toHaveBeenCalledWith(
-        'http://127.0.0.1:3000/session/ses_abc123/prompt_async',
+        "http://127.0.0.1:3000/session/ses_abc123/prompt_async",
         {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
+          method: "POST",
+          headers: { "Content-Type": "application/json" },
           body: JSON.stringify({
-            parts: [{ type: 'text', text: 'Hello OpenCode' }],
+            parts: [{ type: "text", text: "Hello OpenCode" }],
           }),
-        }
+        },
       );
     });
 
-    it('should include model in payload when provided', async () => {
+    it("should include model in payload when provided", async () => {
       mockFetch.mockResolvedValueOnce({
         ok: true,
         status: 204,
       });
 
-      await sendPrompt(3000, 'ses_abc123', 'Hello OpenCode', 'llm-proxy/ant_gemini-3-flash');
+      await sendPrompt(
+        3000,
+        "ses_abc123",
+        "Hello OpenCode",
+        "llm-proxy/ant_gemini-3-flash",
+      );
 
       expect(mockFetch).toHaveBeenCalledWith(
-        'http://127.0.0.1:3000/session/ses_abc123/prompt_async',
+        "http://127.0.0.1:3000/session/ses_abc123/prompt_async",
         {
-          method: 'POST',
-          headers: { 'Content-Type': 'application/json' },
+          method: "POST",
+          headers: { "Content-Type": "application/json" },
           body: JSON.stringify({
-            parts: [{ type: 'text', text: 'Hello OpenCode' }],
-            model: { providerID: 'llm-proxy', modelID: 'ant_gemini-3-flash' },
+            parts: [{ type: "text", text: "Hello OpenCode" }],
+            model: { providerID: "llm-proxy", modelID: "ant_gemini-3-flash" },
           }),
-        }
+        },
       );
     });
 
-    it('should throw error if HTTP request fails', async () => {
+    it("should throw error if HTTP request fails", async () => {
       mockFetch.mockResolvedValueOnce({
         ok: false,
         status: 404,
-        statusText: 'Not Found',
-        text: async () => 'Not Found',
+        statusText: "Not Found",
+        text: async () => "Not Found",
       });
 
-      await expect(sendPrompt(3000, 'ses_invalid', 'test')).rejects.toThrow(
-        'Failed to send prompt: 404 Not Found — Not Found'
+      await expect(sendPrompt(3000, "ses_invalid", "test")).rejects.toThrow(
+        "Failed to send prompt: 404 Not Found — Not Found",
       );
     });
   });
 
-  describe('thread-session mapping', () => {
-    it('should store and retrieve session for thread', () => {
-      setSessionForThread('thread1', 'ses_123', '/path/to/project', 4000);
+  describe("thread-session mapping", () => {
+    it("should store and retrieve session for thread", () => {
+      setSessionForThread("thread1", "ses_123", "/path/to/project", 4000);
 
-      const result = getSessionForThread('thread1');
+      const result = getSessionForThread("thread1");
 
-      expect(result).toEqual({ sessionId: 'ses_123', projectPath: '/path/to/project', port: 4000 });
+      expect(result).toEqual({
+        sessionId: "ses_123",
+        projectPath: "/path/to/project",
+        port: 4000,
+      });
     });
 
-    it('should return undefined for unknown thread', () => {
-      const result = getSessionForThread('unknown_thread');
+    it("should return undefined for unknown thread", () => {
+      const result = getSessionForThread("unknown_thread");
 
       expect(result).toBeUndefined();
     });
 
-    it('should clear session for thread', () => {
-      setSessionForThread('thread2', 'ses_456', '/path/to/project2', 4001);
+    it("should clear session for thread", () => {
+      setSessionForThread("thread2", "ses_456", "/path/to/project2", 4001);
 
-      clearSessionForThread('thread2');
+      clearSessionForThread("thread2");
 
-      const result = getSessionForThread('thread2');
+      const result = getSessionForThread("thread2");
       expect(result).toBeUndefined();
     });
 
-    it('should overwrite existing session for thread', () => {
-      setSessionForThread('thread3', 'ses_old', '/path/to/old', 4002);
-      setSessionForThread('thread3', 'ses_new', '/path/to/new', 4003);
+    it("should overwrite existing session for thread", () => {
+      setSessionForThread("thread3", "ses_old", "/path/to/old", 4002);
+      setSessionForThread("thread3", "ses_new", "/path/to/new", 4003);
 
-      const result = getSessionForThread('thread3');
+      const result = getSessionForThread("thread3");
 
-      expect(result).toEqual({ sessionId: 'ses_new', projectPath: '/path/to/new', port: 4003 });
+      expect(result).toEqual({
+        sessionId: "ses_new",
+        projectPath: "/path/to/new",
+        port: 4003,
+      });
     });
 
-    it('should preserve createdAt when updating an existing thread session', () => {
-      setSessionForThread('thread4', 'ses_original', '/path/to/project', 4004);
-      const original = dataStoreMock.getThreadSession('thread4');
+    it("should preserve createdAt when updating an existing thread session", () => {
+      setSessionForThread("thread4", "ses_original", "/path/to/project", 4004);
+      const original = dataStoreMock.getThreadSession("thread4");
 
-      setSessionForThread('thread4', 'ses_original', '/path/to/project', 4005);
+      setSessionForThread("thread4", "ses_original", "/path/to/project", 4005);
 
-      const updated = dataStoreMock.getThreadSession('thread4');
+      const updated = dataStoreMock.getThreadSession("thread4");
       expect(updated?.createdAt).toBe(original?.createdAt);
       expect(updated?.port).toBe(4005);
     });
   });
 
-  describe('ensureSessionForThread', () => {
-    it('should reuse and refresh an existing valid session', async () => {
-      setSessionForThread('thread5', 'ses_valid', '/path/to/project', 4000);
-      const original = dataStoreMock.getThreadSession('thread5');
+  describe("ensureSessionForThread", () => {
+    it("should reuse and refresh an existing valid session", async () => {
+      setSessionForThread("thread5", "ses_valid", "/path/to/project", 4000);
+      const original = dataStoreMock.getThreadSession("thread5");
 
       mockFetch.mockResolvedValueOnce({ ok: true });
 
-      const sessionId = await ensureSessionForThread('thread5', '/path/to/project', 4010);
+      const sessionId = await ensureSessionForThread(
+        "thread5",
+        "/path/to/project",
+        4010,
+      );
 
-      const updated = dataStoreMock.getThreadSession('thread5');
-      expect(sessionId).toBe('ses_valid');
-      expect(updated?.sessionId).toBe('ses_valid');
+      const updated = dataStoreMock.getThreadSession("thread5");
+      expect(sessionId).toBe("ses_valid");
+      expect(updated?.sessionId).toBe("ses_valid");
       expect(updated?.port).toBe(4010);
       expect(updated?.createdAt).toBe(original?.createdAt);
     });
 
-    it('should create and persist a new session when the stored one is invalid', async () => {
-      setSessionForThread('thread6', 'ses_stale', '/path/to/project', 4000);
-      const original = dataStoreMock.getThreadSession('thread6');
+    it("should create and persist a new session when the stored one is invalid", async () => {
+      setSessionForThread("thread6", "ses_stale", "/path/to/project", 4000);
+      const original = dataStoreMock.getThreadSession("thread6");
 
       mockFetch
-        .mockResolvedValueOnce({ ok: false, status: 404, statusText: 'Not Found' })
-        .mockResolvedValueOnce({ ok: true, json: async () => ({ id: 'ses_new' }) });
-
-      const sessionId = await ensureSessionForThread('thread6', '/path/to/project', 4011);
+        .mockResolvedValueOnce({
+          ok: false,
+          status: 404,
+          statusText: "Not Found",
+        })
+        .mockResolvedValueOnce({
+          ok: true,
+          json: async () => ({ id: "ses_new" }),
+        });
+
+      const sessionId = await ensureSessionForThread(
+        "thread6",
+        "/path/to/project",
+        4011,
+      );
 
-      const updated = dataStoreMock.getThreadSession('thread6');
-      expect(sessionId).toBe('ses_new');
-      expect(updated?.sessionId).toBe('ses_new');
+      const updated = dataStoreMock.getThreadSession("thread6");
+      expect(sessionId).toBe("ses_new");
+      expect(updated?.sessionId).toBe("ses_new");
       expect(updated?.port).toBe(4011);
       expect(updated?.createdAt).toBe(original?.createdAt);
     });
 
-    it('should create a new session when the stored project path no longer matches', async () => {
-      setSessionForThread('thread7', 'ses_old', '/path/to/old', 4000);
+    it("should create a new session when the stored project path no longer matches", async () => {
+      setSessionForThread("thread7", "ses_old", "/path/to/old", 4000);
 
-      mockFetch.mockResolvedValueOnce({ ok: true, json: async () => ({ id: 'ses_project_new' }) });
+      mockFetch.mockResolvedValueOnce({
+        ok: true,
+        json: async () => ({ id: "ses_project_new" }),
+      });
 
-      const sessionId = await ensureSessionForThread('thread7', '/path/to/new', 4012);
+      const sessionId = await ensureSessionForThread(
+        "thread7",
+        "/path/to/new",
+        4012,
+      );
 
-      const updated = dataStoreMock.getThreadSession('thread7');
-      expect(sessionId).toBe('ses_project_new');
-      expect(updated?.sessionId).toBe('ses_project_new');
-      expect(updated?.projectPath).toBe('/path/to/new');
+      const updated = dataStoreMock.getThreadSession("thread7");
+      expect(sessionId).toBe("ses_project_new");
+      expect(updated?.sessionId).toBe("ses_project_new");
+      expect(updated?.projectPath).toBe("/path/to/new");
       expect(updated?.port).toBe(4012);
       expect(mockFetch).toHaveBeenCalledTimes(1);
     });
   });
 
-  describe('SSEClient management', () => {
-    it('should store and retrieve SSEClient for thread', () => {
+  describe("SSEClient management", () => {
+    it("should store and retrieve SSEClient for thread", () => {
       const mockClient = new SSEClient();
 
-      setSseClient('thread1', mockClient);
+      setSseClient("thread1", mockClient);
 
-      const result = getSseClient('thread1');
+      const result = getSseClient("thread1");
 
       expect(result).toBe(mockClient);
     });
 
-    it('should return undefined for unknown thread', () => {
-      const result = getSseClient('unknown_thread');
+    it("should return undefined for unknown thread", () => {
+      const result = getSseClient("unknown_thread");
 
       expect(result).toBeUndefined();
     });
 
-    it('should clear SSEClient for thread', () => {
+    it("should clear SSEClient for thread", () => {
       const mockClient = new SSEClient();
-      setSseClient('thread2', mockClient);
+      setSseClient("thread2", mockClient);
 
-      clearSseClient('thread2');
+      clearSseClient("thread2");
 
-      const result = getSseClient('thread2');
+      const result = getSseClient("thread2");
       expect(result).toBeUndefined();
     });
 
-    it('should overwrite existing SSEClient for thread', () => {
+    it("should overwrite existing SSEClient for thread", () => {
       const mockClient1 = new SSEClient();
       const mockClient2 = new SSEClient();
 
-      setSseClient('thread3', mockClient1);
-      setSseClient('thread3', mockClient2);
+      setSseClient("thread3", mockClient1);
+      setSseClient("thread3", mockClient2);
 
-      const result = getSseClient('thread3');
+      const result = getSseClient("thread3");
 
       expect(result).toBe(mockClient2);
     });
   });
 
-  describe('integration', () => {
-    it('should manage both session and SSEClient independently', () => {
+  describe("integration", () => {
+    it("should manage both session and SSEClient independently", () => {
       const mockClient = new SSEClient();
 
-      setSessionForThread('thread1', 'ses_123', '/path/to/project', 4000);
-      setSseClient('thread1', mockClient);
+      setSessionForThread("thread1", "ses_123", "/path/to/project", 4000);
+      setSseClient("thread1", mockClient);
 
-      expect(getSessionForThread('thread1')).toEqual({
-        sessionId: 'ses_123',
-        projectPath: '/path/to/project',
+      expect(getSessionForThread("thread1")).toEqual({
+        sessionId: "ses_123",
+        projectPath: "/path/to/project",
         port: 4000,
       });
-      expect(getSseClient('thread1')).toBe(mockClient);
+      expect(getSseClient("thread1")).toBe(mockClient);
+
+      clearSessionForThread("thread1");
+
+      expect(getSessionForThread("thread1")).toBeUndefined();
+      expect(getSseClient("thread1")).toBe(mockClient); // SSEClient still exists
+
+      clearSseClient("thread1");
+
+      expect(getSseClient("thread1")).toBeUndefined();
+    });
+  });
+
+  describe("OPENCODE_SERVER_PASSWORD auth", () => {
+    it("does not send Authorization header when env is unset (current behavior)", async () => {
+      mockFetch.mockResolvedValueOnce({
+        ok: true,
+        json: async () => ({ id: "ses_new" }),
+      });
+
+      await createSession(3000);
+
+      const [, init] = mockFetch.mock.calls[0] as [string, RequestInit];
+      expect(init.headers).toEqual({ "Content-Type": "application/json" });
+      expect(
+        (init.headers as Record<string, string>).Authorization,
+      ).toBeUndefined();
+    });
 
-      clearSessionForThread('thread1');
+    it("sends a Basic Authorization header on createSession when password is set", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "s3cret";
+      mockFetch.mockResolvedValueOnce({
+        ok: true,
+        json: async () => ({ id: "ses_new" }),
+      });
+
+      await createSession(3000);
+
+      const [, init] = mockFetch.mock.calls[0] as [string, RequestInit];
+      const expected = `Basic ${Buffer.from("opencode:s3cret").toString("base64")}`;
+      expect((init.headers as Record<string, string>).Authorization).toBe(
+        expected,
+      );
+    });
 
-      expect(getSessionForThread('thread1')).toBeUndefined();
-      expect(getSseClient('thread1')).toBe(mockClient); // SSEClient still exists
+    it("uses OPENCODE_SERVER_USERNAME in the Basic token when provided", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "s3cret";
+      process.env.OPENCODE_SERVER_USERNAME = "alice";
+      mockFetch.mockResolvedValueOnce({ ok: true, status: 204 });
 
-      clearSseClient('thread1');
+      await sendPrompt(3000, "ses_abc", "hi");
 
-      expect(getSseClient('thread1')).toBeUndefined();
+      const [, init] = mockFetch.mock.calls[0] as [string, RequestInit];
+      const expected = `Basic ${Buffer.from("alice:s3cret").toString("base64")}`;
+      expect((init.headers as Record<string, string>).Authorization).toBe(
+        expected,
+      );
+    });
+
+    it("throws a clear credential-mismatch error on 401 when password is set", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "wrong";
+      mockFetch.mockResolvedValueOnce({
+        ok: false,
+        status: 401,
+        statusText: "Unauthorized",
+      });
+
+      await expect(createSession(3000)).rejects.toThrow(
+        /rejected credentials/i,
+      );
+    });
+
+    it("throws a clear missing-password error on 401 when password is unset", async () => {
+      mockFetch.mockResolvedValueOnce({
+        ok: false,
+        status: 401,
+        statusText: "Unauthorized",
+      });
+
+      await expect(createSession(3000)).rejects.toThrow(
+        /OPENCODE_SERVER_PASSWORD is not set/i,
+      );
+    });
+
+    it("surfaces auth errors from validateSession instead of returning false", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "wrong";
+      mockFetch.mockResolvedValueOnce({
+        ok: false,
+        status: 401,
+        statusText: "Unauthorized",
+      });
+
+      await expect(validateSession(3000, "ses_abc")).rejects.toThrow(
+        /rejected credentials/i,
+      );
+    });
+
+    it("surfaces auth errors from getSessionInfo instead of returning null", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "wrong";
+      mockFetch.mockResolvedValueOnce({
+        ok: false,
+        status: 401,
+        statusText: "Unauthorized",
+      });
+
+      await expect(getSessionInfo(3000, "ses_abc")).rejects.toThrow(
+        /rejected credentials/i,
+      );
+    });
+
+    it("surfaces auth errors from listSessions instead of returning an empty list", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "wrong";
+      mockFetch.mockResolvedValueOnce({
+        ok: false,
+        status: 401,
+        statusText: "Unauthorized",
+      });
+
+      await expect(listSessions(3000)).rejects.toThrow(
+        /rejected credentials/i,
+      );
+    });
+
+    it("surfaces auth errors from abortSession instead of returning false", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "wrong";
+      mockFetch.mockResolvedValueOnce({
+        ok: false,
+        status: 401,
+        statusText: "Unauthorized",
+      });
+
+      await expect(abortSession(3000, "ses_abc")).rejects.toThrow(
+        /rejected credentials/i,
+      );
+    });
+
+    it("keeps existing fallback values for non-auth HTTP failures", async () => {
+      mockFetch
+        .mockResolvedValueOnce({ ok: false, status: 404 })
+        .mockResolvedValueOnce({ ok: false, status: 404 })
+        .mockResolvedValueOnce({ ok: false, status: 404 })
+        .mockResolvedValueOnce({ ok: false, status: 404 });
+
+      await expect(validateSession(3000, "ses_missing")).resolves.toBe(false);
+      await expect(getSessionInfo(3000, "ses_missing")).resolves.toBeNull();
+      await expect(listSessions(3000)).resolves.toEqual([]);
+      await expect(abortSession(3000, "ses_missing")).resolves.toBe(false);
+    });
+
+    it("keeps existing fallback values for transport failures", async () => {
+      mockFetch
+        .mockRejectedValueOnce(new Error("ECONNREFUSED"))
+        .mockRejectedValueOnce(new Error("ECONNREFUSED"))
+        .mockRejectedValueOnce(new Error("ECONNREFUSED"))
+        .mockRejectedValueOnce(new Error("ECONNREFUSED"));
+
+      await expect(validateSession(3000, "ses_abc")).resolves.toBe(false);
+      await expect(getSessionInfo(3000, "ses_abc")).resolves.toBeNull();
+      await expect(listSessions(3000)).resolves.toEqual([]);
+      await expect(abortSession(3000, "ses_abc")).resolves.toBe(false);
     });
   });
 });
diff --git a/src/__tests__/sseClient.test.ts b/src/__tests__/sseClient.test.ts
index b970a6d..441f75e 100644
--- a/src/__tests__/sseClient.test.ts
+++ b/src/__tests__/sseClient.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
 
 const mockEventSourceInstance = {
   addEventListener: vi.fn(),
@@ -6,79 +6,99 @@ const mockEventSourceInstance = {
   readyState: 1,
 };
 
-vi.mock('eventsource', () => {
-  const EventSourceMock: any = vi.fn().mockImplementation(function() {
+vi.mock("eventsource", () => {
+  const EventSourceMock: any = vi.fn().mockImplementation(function () {
     return mockEventSourceInstance;
   });
   EventSourceMock.OPEN = 1;
   EventSourceMock.CLOSED = 2;
   EventSourceMock.CONNECTING = 0;
-  
+
   return {
     EventSource: EventSourceMock,
   };
 });
 
-import { SSEClient } from '../services/sseClient.js';
-import { EventSource } from 'eventsource';
+import { SSEClient } from "../services/sseClient.js";
+import { EventSource } from "eventsource";
 
 const MockEventSource = EventSource as unknown as ReturnType<typeof vi.fn>;
 
-describe('SSEClient', () => {
+describe("SSEClient", () => {
   let client: SSEClient;
+  const originalPassword = process.env.OPENCODE_SERVER_PASSWORD;
+  const originalUsername = process.env.OPENCODE_SERVER_USERNAME;
 
   beforeEach(() => {
     mockEventSourceInstance.addEventListener = vi.fn();
     mockEventSourceInstance.close = vi.fn();
     mockEventSourceInstance.readyState = 1;
     MockEventSource.mockClear();
+    delete process.env.OPENCODE_SERVER_PASSWORD;
+    delete process.env.OPENCODE_SERVER_USERNAME;
     client = new SSEClient();
   });
 
   afterEach(() => {
     client.disconnect();
+    if (originalPassword === undefined)
+      delete process.env.OPENCODE_SERVER_PASSWORD;
+    else process.env.OPENCODE_SERVER_PASSWORD = originalPassword;
+    if (originalUsername === undefined)
+      delete process.env.OPENCODE_SERVER_USERNAME;
+    else process.env.OPENCODE_SERVER_USERNAME = originalUsername;
   });
 
-  describe('connect', () => {
-    it('should connect to SSE endpoint', () => {
-      client.connect('http://127.0.0.1:3000');
+  describe("connect", () => {
+    it("should connect to SSE endpoint", () => {
+      client.connect("http://127.0.0.1:3000");
 
-      expect(MockEventSource).toHaveBeenCalledWith('http://127.0.0.1:3000/event');
+      expect(MockEventSource).toHaveBeenCalledWith(
+        "http://127.0.0.1:3000/event",
+        undefined,
+      );
     });
 
-    it('should set up message event listener', () => {
-      client.connect('http://127.0.0.1:3000');
+    it("should set up message event listener", () => {
+      client.connect("http://127.0.0.1:3000");
 
-      expect(mockEventSourceInstance.addEventListener).toHaveBeenCalledWith('message', expect.any(Function));
+      expect(mockEventSourceInstance.addEventListener).toHaveBeenCalledWith(
+        "message",
+        expect.any(Function),
+      );
     });
 
-    it('should set up error event listener', () => {
-      client.connect('http://127.0.0.1:3000');
+    it("should set up error event listener", () => {
+      client.connect("http://127.0.0.1:3000");
 
-      expect(mockEventSourceInstance.addEventListener).toHaveBeenCalledWith('error', expect.any(Function));
+      expect(mockEventSourceInstance.addEventListener).toHaveBeenCalledWith(
+        "error",
+        expect.any(Function),
+      );
     });
   });
 
-  describe('onPartUpdated', () => {
-    it('should trigger callback for text part updates', () => {
+  describe("onPartUpdated", () => {
+    it("should trigger callback for text part updates", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onPartUpdated(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'message.part.updated',
+          type: "message.part.updated",
           properties: {
             part: {
-              type: 'text',
-              id: 'part-1',
-              sessionID: 'session-1',
-              messageID: 'msg-1',
-              text: 'Hello, world!',
+              type: "text",
+              id: "part-1",
+              sessionID: "session-1",
+              messageID: "msg-1",
+              text: "Hello, world!",
             },
           },
         }),
@@ -87,31 +107,32 @@ describe('SSEClient', () => {
       messageHandler(event);
 
       expect(callback).toHaveBeenCalledWith({
-        id: 'part-1',
-        sessionID: 'session-1',
-        messageID: 'msg-1',
-        text: 'Hello, world!',
+        id: "part-1",
+        sessionID: "session-1",
+        messageID: "msg-1",
+        text: "Hello, world!",
       });
     });
 
-    it('should not trigger callback for non-text parts', () => {
+    it("should not trigger callback for non-text parts", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onPartUpdated(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'message.part.updated',
+          type: "message.part.updated",
           properties: {
             part: {
-              type: 'tool',
-              id: 'part-1',
-              sessionID: 'session-1',
-              messageID: 'msg-1',
+              type: "tool",
+              id: "part-1",
+              sessionID: "session-1",
+              messageID: "msg-1",
             },
           },
         }),
@@ -122,18 +143,19 @@ describe('SSEClient', () => {
       expect(callback).not.toHaveBeenCalled();
     });
 
-    it('should not trigger callback for non-part-updated events', () => {
+    it("should not trigger callback for non-part-updated events", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onPartUpdated(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'other.event',
+          type: "other.event",
           properties: {},
         }),
       };
@@ -144,42 +166,44 @@ describe('SSEClient', () => {
     });
   });
 
-  describe('onSessionIdle', () => {
-    it('should trigger callback for session.idle events', () => {
+  describe("onSessionIdle", () => {
+    it("should trigger callback for session.idle events", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onSessionIdle(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'session.idle',
+          type: "session.idle",
           properties: {
-            sessionID: 'session-1',
+            sessionID: "session-1",
           },
         }),
       };
 
       messageHandler(event);
 
-      expect(callback).toHaveBeenCalledWith('session-1');
+      expect(callback).toHaveBeenCalledWith("session-1");
     });
 
-    it('should not trigger callback for non-idle events', () => {
+    it("should not trigger callback for non-idle events", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onSessionIdle(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'other.event',
+          type: "other.event",
           properties: {},
         }),
       };
@@ -190,26 +214,27 @@ describe('SSEClient', () => {
     });
   });
 
-  describe('onSessionError', () => {
-    it('should trigger callback for session.error events with ProviderAuthError', () => {
+  describe("onSessionError", () => {
+    it("should trigger callback for session.error events with ProviderAuthError", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onSessionError(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'session.error',
+          type: "session.error",
           properties: {
-            sessionID: 'session-1',
+            sessionID: "session-1",
             error: {
-              name: 'ProviderAuthError',
+              name: "ProviderAuthError",
               data: {
-                message: 'Invalid model ID',
-                providerID: 'ollama',
+                message: "Invalid model ID",
+                providerID: "ollama",
               },
             },
           },
@@ -218,33 +243,34 @@ describe('SSEClient', () => {
 
       messageHandler(event);
 
-      expect(callback).toHaveBeenCalledWith('session-1', {
-        name: 'ProviderAuthError',
+      expect(callback).toHaveBeenCalledWith("session-1", {
+        name: "ProviderAuthError",
         data: {
-          message: 'Invalid model ID',
-          providerID: 'ollama',
+          message: "Invalid model ID",
+          providerID: "ollama",
         },
       });
     });
 
-    it('should trigger callback for session.error events with UnknownError', () => {
+    it("should trigger callback for session.error events with UnknownError", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onSessionError(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'session.error',
+          type: "session.error",
           properties: {
-            sessionID: 'session-2',
+            sessionID: "session-2",
             error: {
-              name: 'UnknownError',
+              name: "UnknownError",
               data: {
-                message: 'Rate limit exceeded',
+                message: "Rate limit exceeded",
               },
             },
           },
@@ -253,28 +279,29 @@ describe('SSEClient', () => {
 
       messageHandler(event);
 
-      expect(callback).toHaveBeenCalledWith('session-2', {
-        name: 'UnknownError',
+      expect(callback).toHaveBeenCalledWith("session-2", {
+        name: "UnknownError",
         data: {
-          message: 'Rate limit exceeded',
+          message: "Rate limit exceeded",
         },
       });
     });
 
-    it('should not trigger callback when error property is missing', () => {
+    it("should not trigger callback when error property is missing", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onSessionError(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'session.error',
+          type: "session.error",
           properties: {
-            sessionID: 'session-1',
+            sessionID: "session-1",
           },
         }),
       };
@@ -284,20 +311,21 @@ describe('SSEClient', () => {
       expect(callback).not.toHaveBeenCalled();
     });
 
-    it('should not trigger callback for non-error events', () => {
+    it("should not trigger callback for non-error events", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onSessionError(callback);
 
-      const messageHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'message'
-      )?.[1];
+      const messageHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "message",
+        )?.[1];
 
       const event = {
         data: JSON.stringify({
-          type: 'session.idle',
+          type: "session.idle",
           properties: {
-            sessionID: 'session-1',
+            sessionID: "session-1",
           },
         }),
       };
@@ -308,53 +336,118 @@ describe('SSEClient', () => {
     });
   });
 
-  describe('onError', () => {
-    it('should trigger callback on error', () => {
+  describe("onError", () => {
+    it("should trigger callback on error", () => {
       const callback = vi.fn();
-      client.connect('http://127.0.0.1:3000');
+      client.connect("http://127.0.0.1:3000");
       client.onError(callback);
 
-      const errorHandler = mockEventSourceInstance.addEventListener.mock.calls.find(
-        (call: any) => call[0] === 'error'
-      )?.[1];
+      const errorHandler =
+        mockEventSourceInstance.addEventListener.mock.calls.find(
+          (call: any) => call[0] === "error",
+        )?.[1];
 
-      const error = new Error('Connection failed');
+      const error = new Error("Connection failed");
       errorHandler(error);
 
       expect(callback).toHaveBeenCalledWith(error);
     });
   });
 
-  describe('disconnect', () => {
-    it('should close the connection', () => {
-      client.connect('http://127.0.0.1:3000');
+  describe("disconnect", () => {
+    it("should close the connection", () => {
+      client.connect("http://127.0.0.1:3000");
       client.disconnect();
 
       expect(mockEventSourceInstance.close).toHaveBeenCalled();
     });
 
-    it('should handle disconnect when not connected', () => {
+    it("should handle disconnect when not connected", () => {
       expect(() => client.disconnect()).not.toThrow();
     });
   });
 
-  describe('isConnected', () => {
-    it('should return true when connected', () => {
-      client.connect('http://127.0.0.1:3000');
+  describe("isConnected", () => {
+    it("should return true when connected", () => {
+      client.connect("http://127.0.0.1:3000");
       mockEventSourceInstance.readyState = 1;
 
       expect(client.isConnected()).toBe(true);
     });
 
-    it('should return false when disconnected', () => {
-      client.connect('http://127.0.0.1:3000');
+    it("should return false when disconnected", () => {
+      client.connect("http://127.0.0.1:3000");
       mockEventSourceInstance.readyState = 2;
 
       expect(client.isConnected()).toBe(false);
     });
 
-    it('should return false when not initialized', () => {
+    it("should return false when not initialized", () => {
       expect(client.isConnected()).toBe(false);
     });
   });
+
+  describe("OPENCODE_SERVER_PASSWORD auth", () => {
+    it("does not pass a custom fetch when password is unset (current behavior)", () => {
+      client.connect("http://127.0.0.1:3000");
+
+      expect(MockEventSource).toHaveBeenCalledWith(
+        "http://127.0.0.1:3000/event",
+        undefined,
+      );
+    });
+
+    it("injects a Basic Authorization header via custom fetch when password is set", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "s3cret";
+
+      client.connect("http://127.0.0.1:3000");
+
+      expect(MockEventSource).toHaveBeenCalledWith(
+        "http://127.0.0.1:3000/event",
+        expect.objectContaining({ fetch: expect.any(Function) }),
+      );
+
+      // Verify the injected fetch actually forwards the Authorization header.
+      const [, init] = MockEventSource.mock.calls[0] as [
+        string,
+        { fetch: typeof fetch },
+      ];
+      const underlyingFetch = vi
+        .fn()
+        .mockResolvedValue({ ok: true } as Response);
+      vi.stubGlobal("fetch", underlyingFetch);
+      await init.fetch("http://127.0.0.1:3000/event", { method: "GET" });
+      vi.unstubAllGlobals();
+
+      const expected = `Basic ${Buffer.from("opencode:s3cret").toString("base64")}`;
+      const call = underlyingFetch.mock.calls[0] as [string, RequestInit];
+      expect((call[1].headers as Record<string, string>).Authorization).toBe(
+        expected,
+      );
+    });
+
+    it("uses OPENCODE_SERVER_USERNAME in the Basic token when provided", async () => {
+      process.env.OPENCODE_SERVER_PASSWORD = "s3cret";
+      process.env.OPENCODE_SERVER_USERNAME = "alice";
+
+      client.connect("http://127.0.0.1:3000");
+
+      const [, init] = MockEventSource.mock.calls[0] as [
+        string,
+        { fetch: typeof fetch },
+      ];
+      const underlyingFetch = vi
+        .fn()
+        .mockResolvedValue({ ok: true } as Response);
+      vi.stubGlobal("fetch", underlyingFetch);
+      await init.fetch("http://127.0.0.1:3000/event", {});
+      vi.unstubAllGlobals();
+
+      const expected = `Basic ${Buffer.from("alice:s3cret").toString("base64")}`;
+      const call = underlyingFetch.mock.calls[0] as [string, RequestInit];
+      expect((call[1].headers as Record<string, string>).Authorization).toBe(
+        expected,
+      );
+    });
+  });
 });
diff --git a/src/services/serveManager.ts b/src/services/serveManager.ts
index 09563b9..8b98d21 100644
--- a/src/services/serveManager.ts
+++ b/src/services/serveManager.ts
@@ -1,22 +1,28 @@
-import { spawn, type ChildProcess } from 'node:child_process';
-import { existsSync } from 'node:fs';
-import { Server } from 'node:net';
-import { delimiter, join } from 'node:path';
-import type { ServeInstance } from '../types/index.js';
-import { getPortConfig } from './configStore.js';
+import { spawn, type ChildProcess } from "node:child_process";
+import { existsSync } from "node:fs";
+import { Server } from "node:net";
+import { delimiter, join } from "node:path";
+import type { ServeInstance } from "../types/index.js";
+import { getPortConfig } from "./configStore.js";
+import { getAuthHeaders, isAuthEnabled } from "./serverAuth.js";
 
 const DEFAULT_PORT_MIN = 14097;
 const DEFAULT_PORT_MAX = 14200;
-const WINDOWS_OPENCODE_COMMANDS = ['opencode.cmd', 'opencode.exe', 'opencode'];
-const POSIX_OPENCODE_COMMANDS = ['opencode'];
+const WINDOWS_OPENCODE_COMMANDS = ["opencode.cmd", "opencode.exe", "opencode"];
+const POSIX_OPENCODE_COMMANDS = ["opencode"];
 
 const instances = new Map<string, ServeInstance>();
 
 function getOpencodeCommandCandidates(): string[] {
-  return process.platform === 'win32' ? WINDOWS_OPENCODE_COMMANDS : POSIX_OPENCODE_COMMANDS;
+  return process.platform === "win32"
+    ? WINDOWS_OPENCODE_COMMANDS
+    : POSIX_OPENCODE_COMMANDS;
 }
 
-function resolveCommandFromPath(command: string, pathValue?: string): string | undefined {
+function resolveCommandFromPath(
+  command: string,
+  pathValue?: string,
+): string | undefined {
   if (!pathValue) {
     return undefined;
   }
@@ -48,49 +54,56 @@ function resolveOpencodeCommand(env: NodeJS.ProcessEnv): string {
   return getOpencodeCommandCandidates()[0];
 }
 
-function formatSpawnError(error: Error, command: string, projectPath: string): string {
+function formatSpawnError(
+  error: Error,
+  command: string,
+  projectPath: string,
+): string {
   const spawnError = error as NodeJS.ErrnoException;
 
   if (!existsSync(projectPath)) {
     return `Project path does not exist or is not accessible: ${projectPath}`;
   }
 
-  if (spawnError.code === 'ENOENT') {
+  if (spawnError.code === "ENOENT") {
     return `OpenCode executable not found: ${command}. Ensure OpenCode is installed and available in PATH for this service.`;
   }
 
-  if (spawnError.code === 'EACCES') {
+  if (spawnError.code === "EACCES") {
     return `OpenCode executable is not accessible: ${command}. Check file permissions and service user access.`;
   }
 
-  return spawnError.message || 'Failed to spawn opencode process';
+  return spawnError.message || "Failed to spawn opencode process";
 }
 
 function isPortAvailable(port: number): Promise<boolean> {
   return new Promise((resolve) => {
     const server = new Server();
-    
-    server.once('error', () => {
+
+    server.once("error", () => {
       resolve(false);
     });
-    
-    server.once('listening', () => {
+
+    server.once("listening", () => {
       server.close(() => {
         resolve(true);
       });
     });
-    
+
     // Bind to 127.0.0.1 explicitly to match opencode serve's default binding
-    server.listen(port, '127.0.0.1');
+    server.listen(port, "127.0.0.1");
   });
 }
 
 async function isOrphanedServerRunning(port: number): Promise<boolean> {
   try {
     const response = await fetch(`http://127.0.0.1:${port}/session`, {
+      headers: getAuthHeaders(),
       signal: AbortSignal.timeout(1000),
     });
-    // If we get any response, there's already a server running
+    // Any HTTP response (including 401 when auth is required) means a server
+    // is already listening on this port. We treat the port as in-use.
+    void response;
     return true;
   } catch {
     return false;
@@ -103,16 +116,20 @@ async function findAvailablePort(): Promise<number> {
   const max = config?.max ?? DEFAULT_PORT_MAX;
 
   for (let port = min; port <= max; port++) {
-    const usedPorts = new Set(Array.from(instances.values()).filter(i => !i.exited).map(i => i.port));
+    const usedPorts = new Set(
+      Array.from(instances.values())
+        .filter((i) => !i.exited)
+        .map((i) => i.port),
+    );
     if (usedPorts.has(port)) {
       continue;
     }
-    
+
     // Check if there's an orphaned opencode server on this port
     if (await isOrphanedServerRunning(port)) {
       continue;
     }
-    
+
     // Check if we can bind to this port
     if (await isPortAvailable(port)) {
       return port;
@@ -124,6 +141,7 @@ async function findAvailablePort(): Promise<number> {
 async function isServerResponding(port: number): Promise<boolean> {
   try {
     const response = await fetch(`http://127.0.0.1:${port}/session`, {
+      headers: getAuthHeaders(),
       signal: AbortSignal.timeout(2000),
     });
     return response.ok;
@@ -136,7 +154,10 @@ function cleanupInstance(key: string): void {
   instances.delete(key);
 }
 
-export async function spawnServe(projectPath: string, model?: string): Promise<number> {
+export async function spawnServe(
+  projectPath: string,
+  model?: string,
+): Promise<number> {
   const key = model ? `${projectPath}:${model}` : projectPath;
   const existing = instances.get(key);
   if (existing && !existing.exited) {
@@ -149,20 +170,20 @@ export async function spawnServe(projectPath: string, model?: string): Promise<n
   }
 
   const port = await findAvailablePort();
-  
+
   // Note: opencode serve doesn't support --model flag
   // Model selection must happen at session/prompt level, not server startup
-  const args = ['serve', '--port', port.toString()];
+  const args = ["serve", "--port", port.toString()];
   const env = { ...process.env };
   const command = resolveOpencodeCommand(env);
-  
-  console.log(`[opencode] Spawning: ${command} ${args.join(' ')}`);
+
+  console.log(`[opencode] Spawning: ${command} ${args.join(" ")}`);
   console.log(`[opencode] Working directory: ${projectPath}`);
-  
+
   const child = spawn(command, args, {
     cwd: projectPath,
     env,
-    stdio: ['inherit', 'pipe', 'pipe'],
+    stdio: ["inherit", "pipe", "pipe"],
   });
 
   const instance: ServeInstance = {
@@ -174,10 +195,10 @@ export async function spawnServe(projectPath: string, model?: string): Promise<n
 
   instances.set(key, instance);
 
-  let stderrBuffer = '';
-  let stdoutBuffer = '';
+  let stderrBuffer = "";
+  let stdoutBuffer = "";
 
-  child.stdout?.on('data', (data) => {
+  child.stdout?.on("data", (data) => {
     const text = data.toString();
     stdoutBuffer += text;
     if (stdoutBuffer.length > 2000) {
@@ -185,8 +206,8 @@ export async function spawnServe(projectPath: string, model?: string): Promise<n
     }
     console.log(`[opencode stdout] ${text.trim()}`);
   });
-  
-  child.stderr?.on('data', (data) => {
+
+  child.stderr?.on("data", (data) => {
     const text = data.toString();
     stderrBuffer += text;
     if (stderrBuffer.length > 2000) {
@@ -195,14 +216,14 @@ export async function spawnServe(projectPath: string, model?: string): Promise<n
     console.error(`[opencode stderr] ${text.trim()}`);
   });
 
-  child.on('exit', (code) => {
+  child.on("exit", (code) => {
     const inst = instances.get(key);
     if (inst) {
       inst.exited = true;
       inst.exitCode = code;
       if (code !== 0 && code !== null) {
         // Combine stdout and stderr for error message
-        const combinedOutput = (stderrBuffer.trim() || stdoutBuffer.trim());
+        const combinedOutput = stderrBuffer.trim() || stdoutBuffer.trim();
         inst.exitError = combinedOutput || `Process exited with code ${code}`;
         console.error(`[opencode] Process exited with code ${code}`);
         if (combinedOutput) {
@@ -212,7 +233,7 @@ export async function spawnServe(projectPath: string, model?: string): Promise<n
     }
   });
 
-  child.on('error', (error) => {
+  child.on("error", (error) => {
     const formattedError = formatSpawnError(error, command, projectPath);
     console.error(`[opencode] Spawn error: ${formattedError}`);
     const inst = instances.get(key);
@@ -225,7 +246,10 @@ export async function spawnServe(projectPath: string, model?: string): Promise<n
   return port;
 }
 
-export function getPort(projectPath: string, model?: string): number | undefined {
+export function getPort(
+  projectPath: string,
+  model?: string,
+): number | undefined {
   const key = model ? `${projectPath}:${model}` : projectPath;
   return instances.get(key)?.port;
 }
@@ -242,28 +266,54 @@ export function stopServe(projectPath: string, model?: string): boolean {
   return true;
 }
 
-export async function waitForReady(port: number, timeout: number = 30000, projectPath?: string, model?: string): Promise<void> {
+export async function waitForReady(
+  port: number,
+  timeout: number = 30000,
+  projectPath?: string,
+  model?: string,
+): Promise<void> {
   const start = Date.now();
   const url = `http://127.0.0.1:${port}/session`;
-  const key = projectPath ? (model ? `${projectPath}:${model}` : projectPath) : null;
+  const key = projectPath
+    ? model
+      ? `${projectPath}:${model}`
+      : projectPath
+    : null;
 
   while (Date.now() - start < timeout) {
     // Check if the process has exited early
     if (key) {
       const instance = instances.get(key);
       if (instance?.exited) {
-        const errorMsg = instance.exitError || `opencode serve exited with code ${instance.exitCode}`;
+        const errorMsg =
+          instance.exitError ||
+          `opencode serve exited with code ${instance.exitCode}`;
         cleanupInstance(key);
         throw new Error(`opencode serve failed to start: ${errorMsg}`);
       }
     }
 
     try {
-      const response = await fetch(url);
+      const response = await fetch(url, { headers: getAuthHeaders() });
       if (response.ok) {
         return;
       }
-    } catch {
+      if (response.status === 401 || response.status === 403) {
+        const hint = isAuthEnabled()
+          ? "credentials were rejected by the opencode server. Verify OPENCODE_SERVER_PASSWORD (and OPENCODE_SERVER_USERNAME if set) match the values opencode serve was started with."
+          : "opencode server requires authentication but OPENCODE_SERVER_PASSWORD is not set in this process.";
+        throw new Error(
+          `opencode serve is running on port ${port} but ${hint}`,
+        );
+      }
+    } catch (err) {
+      // Surface auth errors immediately instead of silently retrying until timeout.
+      if (
+        err instanceof Error &&
+        err.message.startsWith("opencode serve is running on port")
+      ) {
+        throw err;
+      }
     }
     await new Promise((resolve) => setTimeout(resolve, 1000));
   }
@@ -272,13 +322,17 @@ export async function waitForReady(port: number, timeout: number = 30000, projec
   if (key) {
     const instance = instances.get(key);
     if (instance?.exited) {
-      const errorMsg = instance.exitError || `opencode serve exited with code ${instance.exitCode}`;
+      const errorMsg =
+        instance.exitError ||
+        `opencode serve exited with code ${instance.exitCode}`;
       cleanupInstance(key);
       throw new Error(`opencode serve failed to start: ${errorMsg}`);
     }
   }
 
-  throw new Error(`Service at port ${port} failed to become ready within ${timeout}ms. Check if 'opencode serve' is working correctly.`);
+  throw new Error(
+    `Service at port ${port} failed to become ready within ${timeout}ms. Check if 'opencode serve' is working correctly.`,
+  );
 }
 
 export function stopAll(): void {
@@ -295,7 +349,12 @@ export function getAllInstances(): Array<{ key: string; port: number }> {
   }));
 }
 
-export function getInstanceState(projectPath: string, model?: string): { exited: boolean; exitCode?: number | null; exitError?: string } | undefined {
+export function getInstanceState(
+  projectPath: string,
+  model?: string,
+):
+  | { exited: boolean; exitCode?: number | null; exitError?: string }
+  | undefined {
   const key = model ? `${projectPath}:${model}` : projectPath;
   const instance = instances.get(key);
   if (!instance) return undefined;
diff --git a/src/services/serverAuth.ts b/src/services/serverAuth.ts
new file mode 100644
index 0000000..2cf3d52
--- /dev/null
+++ b/src/services/serverAuth.ts
@@ -0,0 +1,76 @@
+/**
+ * Upstream OpenCode server authentication support.
+ *
+ * Mirrors the behavior of `opencode serve` when the following environment
+ * variables are set:
+ *   - OPENCODE_SERVER_PASSWORD — enables HTTP Basic auth on the server
+ *   - OPENCODE_SERVER_USERNAME — optional, defaults to "opencode"
+ *
+ * When neither is set, this module is a no-op and existing behavior is
+ * preserved. We never introduce our own auth mechanism; we only forward the
+ * upstream credentials to the local opencode server so internal requests
+ * (session HTTP calls, SSE /event stream, readiness checks) continue to work.
+ *
+ * This is optional hardening / compatibility for users who already rely on
+ * upstream `OPENCODE_SERVER_PASSWORD`. It is not a replacement for the
+ * Discord-side allowlist or other access controls.
+ */
+
+const DEFAULT_USERNAME = "opencode";
+
+function getPassword(): string | undefined {
+  const pw = process.env.OPENCODE_SERVER_PASSWORD;
+  return pw && pw.length > 0 ? pw : undefined;
+}
+
+function getUsername(): string {
+  const user = process.env.OPENCODE_SERVER_USERNAME;
+  return user && user.length > 0 ? user : DEFAULT_USERNAME;
+}
+
+/**
+ * Returns the base64 encoded `username:password` token expected by the
+ * upstream `auth_token` query parameter, or undefined when auth is disabled.
+ */
+export function getAuthToken(): string | undefined {
+  const pw = getPassword();
+  if (!pw) return undefined;
+  return Buffer.from(`${getUsername()}:${pw}`).toString("base64");
+}
+
+/**
+ * Returns true when OPENCODE_SERVER_PASSWORD is set.
+ */
+export function isAuthEnabled(): boolean {
+  return getPassword() !== undefined;
+}
+
+/**
+ * Returns an object with an `Authorization: Basic <token>` header when auth
+ * is enabled, or an empty object otherwise. Safe to spread into fetch headers.
+ */
+export function getAuthHeaders(): Record<string, string> {
+  const token = getAuthToken();
+  if (!token) return {};
+  return { Authorization: `Basic ${token}` };
+}
+
+/**
+ * Throws a clear, actionable error when a response indicates an auth
+ * misconfiguration so the user does not see a vague connection failure.
+ */
+export function assertNotAuthError(status: number, context: string): void {
+  if (status !== 401 && status !== 403) return;
+  if (isAuthEnabled()) {
+    throw new Error(
+      `${context}: opencode server rejected credentials (HTTP ${status}). ` +
+        `Check that OPENCODE_SERVER_PASSWORD (and OPENCODE_SERVER_USERNAME if set) ` +
+        `match the values the opencode server was started with.`,
+    );
+  }
+  throw new Error(
+    `${context}: opencode server requires authentication (HTTP ${status}) but ` +
+      `OPENCODE_SERVER_PASSWORD is not set in this process. Set it to the same ` +
+      `value the opencode server was started with.`,
+  );
+}
diff --git a/src/services/sessionManager.ts b/src/services/sessionManager.ts
index 2a16384..31be14a 100644
--- a/src/services/sessionManager.ts
+++ b/src/services/sessionManager.ts
@@ -1,33 +1,43 @@
-import type { SSEClient } from './sseClient.js';
-import * as dataStore from './dataStore.js';
-import { sanitizeModel } from '../utils/stringUtils.js';
+import type { SSEClient } from "./sseClient.js";
+import * as dataStore from "./dataStore.js";
+import { sanitizeModel } from "../utils/stringUtils.js";
+import { getAuthHeaders, assertNotAuthError } from "./serverAuth.js";
 
 const threadSseClients = new Map<string, SSEClient>();
 
+function jsonHeaders(): Record<string, string> {
+  return { "Content-Type": "application/json", ...getAuthHeaders() };
+}
+
 export async function createSession(port: number): Promise<string> {
   const url = `http://127.0.0.1:${port}/session`;
   const response = await fetch(url, {
-    method: 'POST',
-    headers: { 'Content-Type': 'application/json' },
-    body: '{}',
+    method: "POST",
+    headers: jsonHeaders(),
+    body: "{}",
   });
 
   if (!response.ok) {
-    throw new Error(`Failed to create session: ${response.status} ${response.statusText}`);
+    assertNotAuthError(response.status, "Failed to create session");
+    throw new Error(
+      `Failed to create session: ${response.status} ${response.statusText}`,
+    );
   }
 
   const data = await response.json();
 
   if (!data.id) {
-    throw new Error('Invalid session response: missing id');
+    throw new Error("Invalid session response: missing id");
   }
 
   return data.id;
 }
 
-function parseModelString(model: string): { providerID: string; modelID: string } | null {
+function parseModelString(
+  model: string,
+): { providerID: string; modelID: string } | null {
   const clean = sanitizeModel(model);
-  const slashIndex = clean.indexOf('/');
+  const slashIndex = clean.indexOf("/");
   if (slashIndex === -1) {
     return null;
   }
@@ -37,10 +47,18 @@ function parseModelString(model: string): { providerID: string; modelID: string
   };
 }
 
-export async function sendPrompt(port: number, sessionId: string, text: string, model?: string): Promise<void> {
+export async function sendPrompt(
+  port: number,
+  sessionId: string,
+  text: string,
+  model?: string,
+): Promise<void> {
   const url = `http://127.0.0.1:${port}/session/${sessionId}/prompt_async`;
-  const body: { parts: { type: string; text: string }[]; model?: { providerID: string; modelID: string } } = {
-    parts: [{ type: 'text', text }],
+  const body: {
+    parts: { type: string; text: string }[];
+    model?: { providerID: string; modelID: string };
+  } = {
+    parts: [{ type: "text", text }],
   };
 
   if (model) {
@@ -52,43 +70,62 @@ export async function sendPrompt(port: number, sessionId: string, text: string,
   }
 
   const response = await fetch(url, {
-    method: 'POST',
-    headers: { 'Content-Type': 'application/json' },
+    method: "POST",
+    headers: jsonHeaders(),
     body: JSON.stringify(body),
   });
 
   if (!response.ok) {
     const responseBody = await response.text();
-    throw new Error(`Failed to send prompt: ${response.status} ${response.statusText} — ${responseBody}`);
+    assertNotAuthError(response.status, "Failed to send prompt");
+    throw new Error(
+      `Failed to send prompt: ${response.status} ${response.statusText} — ${responseBody}`,
+    );
   }
 }
 
-export async function validateSession(port: number, sessionId: string): Promise<boolean> {
+export async function validateSession(
+  port: number,
+  sessionId: string,
+): Promise<boolean> {
+  const url = `http://127.0.0.1:${port}/session/${sessionId}`;
+  let response: Response;
   try {
-    const url = `http://127.0.0.1:${port}/session/${sessionId}`;
-    const response = await fetch(url, {
-      method: 'GET',
-      headers: { 'Content-Type': 'application/json' },
+    response = await fetch(url, {
+      method: "GET",
+      headers: jsonHeaders(),
     });
-    return response.ok;
   } catch {
     return false;
   }
+
+  if (!response.ok) {
+    assertNotAuthError(response.status, "Failed to validate session");
+  }
+  return response.ok;
 }
 
-export async function getSessionInfo(port: number, sessionId: string): Promise<SessionInfo | null> {
+export async function getSessionInfo(
+  port: number,
+  sessionId: string,
+): Promise<SessionInfo | null> {
+  const url = `http://127.0.0.1:${port}/session/${sessionId}`;
+  let response: Response;
   try {
-    const url = `http://127.0.0.1:${port}/session/${sessionId}`;
-    const response = await fetch(url, {
-      method: 'GET',
-      headers: { 'Content-Type': 'application/json' },
+    response = await fetch(url, {
+      method: "GET",
+      headers: jsonHeaders(),
     });
-    if (!response.ok) return null;
-    const data = await response.json();
-    return { id: data.id, title: data.title ?? '' };
   } catch {
     return null;
   }
+
+  if (!response.ok) {
+    assertNotAuthError(response.status, "Failed to get session info");
+    return null;
+  }
+  const data = await response.json();
+  return { id: data.id, title: data.title ?? "" };
 }
 
 export interface SessionInfo {
@@ -97,49 +134,71 @@ export interface SessionInfo {
 }
 
 export async function listSessions(port: number): Promise<SessionInfo[]> {
+  const url = `http://127.0.0.1:${port}/session`;
+  let response: Response;
   try {
-    const url = `http://127.0.0.1:${port}/session`;
-    const response = await fetch(url, {
-      method: 'GET',
-      headers: { 'Content-Type': 'application/json' },
+    response = await fetch(url, {
+      method: "GET",
+      headers: jsonHeaders(),
     });
-    
-    if (!response.ok) {
-      return [];
-    }
-    
-    const data = await response.json();
-    if (Array.isArray(data)) {
-      return data.map((s: { id: string; title?: string }) => ({
-        id: s.id,
-        title: s.title ?? '',
-      }));
-    }
-    return [];
   } catch {
     return [];
   }
+
+  if (!response.ok) {
+    assertNotAuthError(response.status, "Failed to list sessions");
+    return [];
+  }
+
+  const data = await response.json();
+  if (Array.isArray(data)) {
+    return data.map((s: { id: string; title?: string }) => ({
+      id: s.id,
+      title: s.title ?? "",
+    }));
+  }
+  return [];
 }
 
-export async function abortSession(port: number, sessionId: string): Promise<boolean> {
+export async function abortSession(
+  port: number,
+  sessionId: string,
+): Promise<boolean> {
+  const url = `http://127.0.0.1:${port}/session/${sessionId}/abort`;
+  let response: Response;
   try {
-    const url = `http://127.0.0.1:${port}/session/${sessionId}/abort`;
-    const response = await fetch(url, {
-      method: 'POST',
+    response = await fetch(url, {
+      method: "POST",
+      headers: getAuthHeaders(),
     });
-    return response.ok;
   } catch {
     return false;
   }
+
+  if (!response.ok) {
+    assertNotAuthError(response.status, "Failed to abort session");
+  }
+  return response.ok;
 }
 
-export function getSessionForThread(threadId: string): { sessionId: string; projectPath: string; port: number } | undefined {
+export function getSessionForThread(
+  threadId: string,
+): { sessionId: string; projectPath: string; port: number } | undefined {
   const session = dataStore.getThreadSession(threadId);
   if (!session) return undefined;
-  return { sessionId: session.sessionId, projectPath: session.projectPath, port: session.port };
+  return {
+    sessionId: session.sessionId,
+    projectPath: session.projectPath,
+    port: session.port,
+  };
 }
 
-export function setSessionForThread(threadId: string, sessionId: string, projectPath: string, port: number): void {
+export function setSessionForThread(
+  threadId: string,
+  sessionId: string,
+  projectPath: string,
+  port: number,
+): void {
   const existing = dataStore.getThreadSession(threadId);
   const now = Date.now();
   dataStore.setThreadSession({
@@ -152,13 +211,22 @@ export function setSessionForThread(threadId: string, sessionId: string, project
   });
 }
 
-export async function ensureSessionForThread(threadId: string, projectPath: string, port: number): Promise<string> {
+export async function ensureSessionForThread(
+  threadId: string,
+  projectPath: string,
+  port: number,
+): Promise<string> {
   const existingSession = getSessionForThread(threadId);
 
   if (existingSession && existingSession.projectPath === projectPath) {
     const isValid = await validateSession(port, existingSession.sessionId);
     if (isValid) {
-      setSessionForThread(threadId, existingSession.sessionId, projectPath, port);
+      setSessionForThread(
+        threadId,
+        existingSession.sessionId,
+        projectPath,
+        port,
+      );
       return existingSession.sessionId;
     }
   }
diff --git a/src/services/sseClient.ts b/src/services/sseClient.ts
index 1663866..43682ae 100644
--- a/src/services/sseClient.ts
+++ b/src/services/sseClient.ts
@@ -1,9 +1,13 @@
-import { EventSource } from 'eventsource';
-import type { TextPart, SSEEvent, SessionErrorInfo } from '../types/index.js';
+import { EventSource } from "eventsource";
+import type { TextPart, SSEEvent, SessionErrorInfo } from "../types/index.js";
+import { getAuthHeaders } from "./serverAuth.js";
 
 type PartUpdatedCallback = (part: TextPart) => void;
 type SessionIdleCallback = (sessionId: string) => void;
-type SessionErrorCallback = (sessionId: string, error: SessionErrorInfo) => void;
+type SessionErrorCallback = (
+  sessionId: string,
+  error: SessionErrorInfo,
+) => void;
 type ErrorCallback = (error: Error) => void;
 
 export class SSEClient {
@@ -15,9 +19,24 @@ export class SSEClient {
 
   connect(baseUrl: string): void {
     const url = `${baseUrl}/event`;
-    this.eventSource = new EventSource(url);
+    // When OPENCODE_SERVER_PASSWORD is set, forward the same Basic auth
+    // header we use for regular HTTP requests. The `eventsource` package
+    // allows overriding the underlying fetch so we can inject headers —
+    // EventSource itself does not accept headers directly.
+    const authHeaders = getAuthHeaders();
+    const init: ConstructorParameters<typeof EventSource>[1] =
+      Object.keys(authHeaders).length > 0
+        ? {
+            fetch: (input, fetchInit) =>
+              fetch(input, {
+                ...fetchInit,
+                headers: { ...fetchInit?.headers, ...authHeaders },
+              }),
+          }
+        : undefined;
+    this.eventSource = new EventSource(url, init);
 
-    this.eventSource.addEventListener('message', (event: MessageEvent) => {
+    this.eventSource.addEventListener("message", (event: MessageEvent) => {
       try {
         const data: SSEEvent = JSON.parse(event.data);
         this.handleMessage(data);
@@ -26,8 +45,10 @@ export class SSEClient {
       }
     });
 
-    this.eventSource.addEventListener('error', (error: Event) => {
-      this.handleError(error instanceof Error ? error : new Error('SSE connection error'));
+    this.eventSource.addEventListener("error", (error: Event) => {
+      this.handleError(
+        error instanceof Error ? error : new Error("SSE connection error"),
+      );
     });
   }
 
@@ -55,13 +76,16 @@ export class SSEClient {
   }
 
   isConnected(): boolean {
-    return this.eventSource !== null && this.eventSource.readyState === EventSource.OPEN;
+    return (
+      this.eventSource !== null &&
+      this.eventSource.readyState === EventSource.OPEN
+    );
   }
 
   private handleMessage(event: SSEEvent): void {
-    if (event.type === 'message.part.updated') {
+    if (event.type === "message.part.updated") {
       const part = (event.properties as any).part;
-      if (part && part.type === 'text') {
+      if (part && part.type === "text") {
         const textPart: TextPart = {
           id: part.id,
           sessionID: part.sessionID,
@@ -70,14 +94,16 @@ export class SSEClient {
         };
         this.partUpdatedCallbacks.forEach((cb) => cb(textPart));
       }
-    } else if (event.type === 'session.idle') {
+    } else if (event.type === "session.idle") {
       const sessionID = (event.properties as any).sessionID;
       if (sessionID) {
         this.sessionIdleCallbacks.forEach((cb) => cb(sessionID));
       }
-    } else if (event.type === 'session.error') {
+    } else if (event.type === "session.error") {
       const sessionID = (event.properties as any).sessionID;
-      const error = (event.properties as any).error as SessionErrorInfo | undefined;
+      const error = (event.properties as any).error as
+        | SessionErrorInfo
+        | undefined;
       if (sessionID && error) {
         this.sessionErrorCallbacks.forEach((cb) => cb(sessionID, error));
       }
