Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

_Development cycle in progress — entries are added as work merges into `release/v3.8.16` and finalized by the release flow._

### 🔧 Bug Fixes

- **fix(mitm):** `getMitmStatus()` in the build-time stub (Docker image) now returns a graceful `{ running: false }` status instead of throwing, so the Agent Bridge UI shows a clean "stopped" state rather than an error banner in containerised deployments. ([#3390](https://github.com/diegosouzapw/OmniRoute/issues/3390))

---

## [3.8.15] — 2026-06-07
Expand Down
21 changes: 10 additions & 11 deletions src/mitm/manager.stub.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Build-time stub for @/mitm/manager, aliased in by Turbopack during `next build`
// (the Docker image build) so native MITM modules aren't bundled. Routes that
// *statically* import @/mitm/manager get this stub baked in and may reach it at
// runtime in the bundled/container build. Exports that have a safe degraded value
// return it (getCachedPassword/setCachedPassword/clearCachedPassword → null/no-op,
// getAllAgentsStatus → empty list) because MITM needs host access the container
// lacks; getMitmStatus/startMitm/stopMitm throw STUB_ERROR since they can't return
// anything meaningful without the real MITM process. Routes that need real MITM at
// runtime dynamic-import @/mitm/manager.runtime (the real module) instead.
// (the Docker image build) so native MITM modules aren't bundled. Exports that have
// a safe degraded value return it (getCachedPassword/setCachedPassword/clearCachedPassword
// → null/no-op, getAllAgentsStatus → empty list, getMitmStatus → stopped status) because
// MITM needs host access the container lacks. startMitm/stopMitm still throw since they
// cannot do anything meaningful without the real MITM process. Routes that need real MITM
// at runtime dynamic-import @/mitm/manager.runtime (the real module) instead.
// Fix #3390: getMitmStatus no longer throws — it returns running=false so the AgentBridge
// UI shows a graceful "stopped" state instead of an error banner in Docker.

const STUB_ERROR =
"MITM manager stub reached at runtime — build alias applied incorrectly. " +
Expand All @@ -15,9 +15,8 @@ const STUB_ERROR =
export const getCachedPassword = () => null;
export const setCachedPassword = (_pwd: string) => {};
export const clearCachedPassword = () => {};
export const getMitmStatus = async () => {
throw new Error(STUB_ERROR);
};
export const getMitmStatus = async () =>
({ running: false, pid: null, dnsConfigured: false, certExists: false }) as const;
// Must be exported or the Turbopack build fails ("Export getAllAgentsStatus doesn't
// exist") — /api/tools/agent-bridge/state imports it statically. Returns the truthful
// empty agent list in the bundled build rather than throwing (see file header). See #3066.
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/mitm-manager-stub.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Regression test for #3390: getMitmStatus() in the Docker build stub must
* return a safe "not available" status object instead of throwing STUB_ERROR.
* When the stub throws, the AgentBridge state route propagates the error to
* the UI, showing a red error banner instead of a graceful "stopped" state.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";

describe("mitm manager.stub — Docker graceful degradation (#3390)", () => {
it("getMitmStatus resolves without throwing", async () => {
const { getMitmStatus } = await import("../../src/mitm/manager.stub.ts");
await assert.doesNotReject(getMitmStatus);
});

it("getMitmStatus returns running=false", async () => {
const { getMitmStatus } = await import("../../src/mitm/manager.stub.ts");
const status = await getMitmStatus();
assert.strictEqual(status.running, false);
});

it("getMitmStatus returns pid=null", async () => {
const { getMitmStatus } = await import("../../src/mitm/manager.stub.ts");
const status = await getMitmStatus();
assert.strictEqual(status.pid, null);
});

it("startMitm still throws (MITM unavailable in container)", async () => {
const { startMitm } = await import("../../src/mitm/manager.stub.ts");
await assert.rejects(async () => startMitm("key", "pwd"), /stub/i);
});

it("getAllAgentsStatus returns empty array", async () => {
const { getAllAgentsStatus } = await import("../../src/mitm/manager.stub.ts");
assert.deepStrictEqual(getAllAgentsStatus(), []);
});
});
Comment on lines +7 to +37

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The project uses Vitest as its primary testing framework (as configured in vitest.config.ts and specified in the repository style guide). Standardizing on Vitest instead of Node's native node:test and node:assert modules ensures consistency across the test suite and provides more idiomatic assertions.

Additionally, we can statically import the stub functions at the top of the file to eliminate the highly redundant dynamic import() calls inside each test block.

import { describe, it, expect } from "vitest";
import { getMitmStatus, startMitm, getAllAgentsStatus } from "../../src/mitm/manager.stub";

describe("mitm manager.stub — Docker graceful degradation (#3390)", () => {
  it("getMitmStatus resolves without throwing", async () => {
    await expect(getMitmStatus()).resolves.not.toThrow();
  });

  it("getMitmStatus returns running=false", async () => {
    const status = await getMitmStatus();
    expect(status.running).toBe(false);
  });

  it("getMitmStatus returns pid=null", async () => {
    const status = await getMitmStatus();
    expect(status.pid).toBeNull();
  });

  it("startMitm still throws (MITM unavailable in container)", async () => {
    await expect(startMitm("key", "pwd")).rejects.toThrow(/stub/i);
  });

  it("getAllAgentsStatus returns empty array", () => {
    expect(getAllAgentsStatus()).toEqual([]);
  });
});