* docs: move superpowers/research artifacts to isolated _tasks repo + docs tree cleanup
- Move docs/superpowers/{plans,specs} and docs/research/* into the gitignored,
separately-versioned _tasks/ repo; untrack the two tracked research design docs.
- Add CLAUDE.md "Planning & Research Artifacts" section overriding the superpowers
default save paths (docs/... -> _tasks/...); align REPOSITORY_MAP and
DOCUMENTATION_OVERHAUL_PLAN with the new convention.
- Drop 4 now-obsolete /api/discovery/* entries from check-docs-symbols allowlist
(stale-enforcement) and refresh code/spec path comments to _tasks/...
- Sweeps in concurrent docs-tree restructuring (root-level provider/guide docs,
compression spec cleanup, .mcp.json.example removal).
* docs: reorganize docs/ tree + fix stale facts across ~26 docs
Phase A — reorganization:
- Move 7 orphan root docs into subfolders (providers/ created; TIERS+USAGE_QUOTA→guides/;
plugins+PLUGIN_SDK→frameworks/); delete 8 obsolete/redundant docs (SUBMIT_PR superseded
by CONTRIBUTING; DOCUMENTATION_OVERHAUL_PLAN; INCIDENT_RESPONSE/PERF_BUDGETS/THREAT_MODEL;
3 ops snapshots). Rebuild README index (was missing ~40 files) + per-folder meta.json nav.
- Clean 14 dangling doc-path references in bin/ ops scripts, scripts/, workflow, tests;
fix the dockerignore-docs-coverage required-docs path (PROVIDERS→providers/CLAUDE_WEB).
Phase B — content accuracy (verified against code, not the audit summary):
- Functional: ENVIRONMENT flag defaults (INPUT_SANITIZER/MCP_ENFORCE_SCOPES=true,
COMPRESS_DESCRIPTIONS=false, dynamic heap); MCP-SERVER notion tool names (omniroute_*→
notion_*) + counts 87→94; coverage gate 75/70→60/60/60/60 (RELEASE_CHECKLIST, COVERAGE_PLAN,
ERROR_SANITIZATION, CONTRIBUTING); pre-push hook description; regenerate PROVIDER_REFERENCE (237).
- Count drift: providers 237, executors 70, migrations 106, db modules 94, oauth 19,
strategies 17, MCP 94, flags 38, TS 6.0, open-sse ~900/services 294 across architecture/
frameworks/ops docs; AUTO-COMBO 9→12 factors w/ correct DEFAULT_WEIGHTS; REASONING +2
patterns; STEALTH UA defaults; AGENT_PROTOCOLS +cursor-cloud/list-capabilities;
LANGUAGE_PACKS +id pack.
- Kept Node 20 (runtime guard accepts 20.20.2+; only engines is stricter) and MCP scopes=13
(mcpScopes.ts) — both were correct in the docs; corrected only the attribution.
* docs: finish content refresh — compression engines, CLI_TOKEN merge, metadata sweep
- Compression: document the additional built-in engines (CCR, headroom, ionizer,
session-dedup) in COMPRESSION_ENGINES; clarify LLMLingua-2 is the ultra-mode SLM
backend + cross-ref the extra engines in EXTENDING_COMPRESSION; add the id
(Indonesian) language pack to LANGUAGE_PACKS.
- AUTO-COMBO: replace the orphan 'How tiers fit' weight table (stale weights) with a
pointer to the canonical 12-factor DEFAULT_WEIGHTS table.
- Security: merge CLI_TOKEN_AUTH.md (legacy 32-char SHA-256 format) into CLI_TOKEN.md
as a 'Legacy format — still accepted' section (server accepts both HMAC + legacy),
delete CLI_TOKEN_AUTH.md, drop it from the index + security nav.
- Metadata: bump stale frontmatter (version/lastUpdated) to 3.8.40/2026-06-28 across the
doc set audited this pass, and normalize the in-body 'Last updated' header lines to match.
* fix(runtime): drop Node 20 from supported range + align all docs/diagrams/counts
- Node minimum is now 22 (aligned with package.json engines). SUPPORTED_NODE_RANGE in
src/shared/utils/nodeRuntimeSupport.ts (and the bin/ mirror) drops the 20.x line →
'>=22.22.2 <23 || >=24.0.0 <27'; getNodeRuntimeSupport now rejects Node 20 as
unsupported-major. Test updated (TDD): node-runtime-support.test.ts asserts Node 20
rejected. Docs aligned (TROUBLESHOOTING ×2, TERMUX, RELEASE_CHECKLIST, CODEBASE,
CLI-TOOLS, README, llm.txt + 42 i18n llm.txt mirrors, skills/cli-serve).
- Diagrams regenerated: mcp-tools-87 -> mcp-tools-94 (34 base + pool 6 = 94) and
auto-combo-9factor -> auto-combo-12factor (correct DEFAULT_WEIGHTS); SVGs re-rendered
via mermaid-cli; doc refs + diagrams/README updated; fixed a pre-existing broken
resilience-3layers image path.
- CLAUDE.md + AGENTS.md aligned to real counts (237 providers, 94 MCP tools / 34 base,
106 migrations, 94 db modules, 12-factor auto-combo, 17 strategies); README provider
count 231 -> 237; executor count corrected to 68 (provider executors, excl base/index)
and OAuth to 18 across architecture docs. check:docs-all now passes (0 strict drift,
0 broken links); removed dead .mcp.json.example doc link.
* fix(services): update installer Node hint to >=22.22.2 (aligned with dropped Node 20)
* docs: realign counts to current release tip after rebase
The release tip advanced while this work was in flight (Gemini CLI provider/executor
removed by #5246, plus other PRs). Re-counted against the current code and updated:
providers 237->236, executors 68->67, OAuth modules 18->17, open-sse services 294->298;
regenerated PROVIDER_REFERENCE.md (236). check:docs-all passes (0 strict drift).
* docs(changelog) + i18n: record Node 20 drop + fix nodeIncompatibleHint
- CHANGELOG: add [3.8.40] entries for the Node 20.x removal (runtime) and the docs
reorganization/accuracy audit.
- i18n: nodeIncompatibleHint across all 42 locales no longer lists Node 20.x as
supported (ASCII + CJK full-width variants), aligned with the dropped Node 20.
* fix(docs): repair CI breakages from the doc moves
- test: cli-plugin-system asserted docs/dev/plugins.md exists; the file moved to
docs/frameworks/PLUGINS.md — point the test at the new path (Unit fast-path 2/2 fix).
- frontmatter: PLUGINS.md and PLUGIN_SDK.md moved into the fumadocs-indexed
docs/frameworks/ which requires a 'title' frontmatter; the missing frontmatter
failed the Next.js MDX build (dast-smoke 'invalid frontmatter'). Added frontmatter
to both, plus the providers/ docs (consistency; that folder is not indexed).
26 KiB
title, version, lastUpdated
| title | version | lastUpdated |
|---|---|---|
| MITM TPROXY Transparent Decrypt | 3.8.40 | 2026-06-28 |
MITM TPROXY Transparent Decrypt
TPROXY transparent decrypt is OmniRoute's 5th capture mode for the
Traffic Inspector / AgentBridge
MITM stack. It intercepts and decrypts local outbound HTTPS traffic on Linux
using kernel TPROXY + policy routing — without spoofing /etc/hosts and
without mutating OS-wide system-proxy settings. It is headless-friendly
(no DNS edits to clean up) and the firewall rules auto-flush on reboot.
Unlike the other capture modes, TPROXY needs no per-host setup: it transparently intercepts arbitrary destination hosts on a target port, terminates TLS with a leaf certificate it issues on the fly per SNI hostname, captures the decrypted exchange, and re-encrypts the request to the original destination.
Linux-only, root-only, opt-in. This mode requires Linux, a native addon built with a C toolchain, and the CAP_NET_ADMIN capability (typically root). It is gated behind the loopback-only AgentBridge API and disabled by default. A trusted MITM CA that can sign any host is a powerful capability — see §6 Security.
Source: src/mitm/tproxy/
API route: GET / POST / DELETE /api/tools/agent-bridge/tproxy
Dashboard toggle: Traffic Inspector → capture-modes toolbar → "TPROXY Decrypt" ⚠
See also: docs/frameworks/TRAFFIC_INSPECTOR.md,
docs/frameworks/AGENTBRIDGE.md
§1 What it is and when to use it
The other four capture modes each have a limitation:
| Mode | How traffic is steered | Limitation |
|---|---|---|
| AgentBridge | /etc/hosts DNS spoof of a fixed host set |
only the registered IDE-agent hosts |
| Custom Hosts | /etc/hosts DNS spoof per host |
one entry per host; sudo to edit hosts |
| HTTP_PROXY | HTTP_PROXY/HTTPS_PROXY env |
only apps that honor the env var |
| System-wide proxy | OS proxy settings | mutates global state; needs revert |
TPROXY transparent decrypt steers traffic at the kernel layer instead. It
marks new local outbound TCP connections to a target port (default 443) in the
mangle OUTPUT chain, an ip rule reroutes the marked packets to local delivery,
and on re-entry the mangle PREROUTING TPROXY target hands them to an
IP_TRANSPARENT listener — which then terminates TLS and captures the plaintext.
Use it when you want to capture and decrypt traffic from a process that:
- talks to a host AgentBridge does not register, and
- does not honor
HTTP_PROXY, and - you do not want to disturb with a system-wide proxy change.
Because interception happens in the kernel, the originating process needs no configuration change — but the process must trust the dynamic CA OmniRoute installs (see §4).
§2 Requirements
| Requirement | Detail |
|---|---|
| OS | Linux only — IP_TRANSPARENT is a Linux-only socket option. The loader returns "unavailable" on every other platform. |
| Privilege | The CAP_NET_ADMIN capability to create the transparent socket and apply iptables/ip rules — in practice, run as root. |
| Native addon | A tiny N-API addon (src/mitm/tproxy/native/transparent.c) must be built or shipped as a prebuild. See §3. |
| Kernel modules | iptables with the TPROXY, mangle, and mark match support (validated against kernel 6.8.0). |
Graceful degradation: if any requirement is missing (non-Linux, no toolchain,
addon not built), the addon loader (src/mitm/tproxy/transparentSocket.ts::loadTransparentAddon)
returns null rather than throwing. The capture-mode status then reports
available: false, the dashboard toggle is disabled with the tooltip
"TPROXY decrypt requires Linux + root + the native addon", and the rest of
OmniRoute keeps working.
§3 The native IP_TRANSPARENT addon
Node's net module cannot setsockopt(IP_TRANSPARENT) before bind(), which
TPROXY requires (otherwise the kernel drops the redirected packets). The addon
(src/mitm/tproxy/native/transparent.c, built via binding.gyp) is a small N-API
module exposing three functions, consumed through transparentSocket.ts:
| Addon function | Socket work | Used for |
|---|---|---|
createTransparentListener(ip, port) |
socket() + SO_REUSEADDR + IP_TRANSPARENT + bind() + listen(), returns the raw fd |
the transparent capture listener (Node adopts the fd via server.listen({ fd })) |
setSocketMark(fd, mark) |
setsockopt SO_MARK on an existing fd |
anti-loop (mark the proxy's own sockets) |
connectMarked(ip, port, mark) |
socket() + SO_MARK before a non-blocking connect(), returns fd |
the re-encrypted upstream forward (the SYN carries the mark) |
The original destination is read from socket.localAddress/localPort — TPROXY
preserves it, so there is no SO_ORIGINAL_DST/NAT lookup.
Building the addon
npm run build:native:tproxy # cd src/mitm/tproxy/native && node-gyp rebuild
# -> native/build/Release/transparent.node
- During
npm run build,scripts/build/build-tproxy-native.mjsrunsnode-gyp rebuild. It is Linux-only and non-fatal — a missing toolchain just leaves the capture mode unavailable. assembleStandalone.mjscopiesbuild/Release/transparent.nodeinto the standalone bundle;transparentSocket.tsresolves it both module-relative and cwd-relative (<cwd>/src/mitm/tproxy/native/...).build/andprebuilds/are git-ignored — the binary is built, never committed.
The loader probes, in priority order:
native/build/Release/transparent.node, then native/prebuilds/transparent.node
(both module-relative and under <cwd>/src/mitm/tproxy/).
§4 The per-SNI dynamic CA and trust-store installer
The static AgentBridge MITM cert works only because AgentBridge DNS-spoofs a fixed host set. TPROXY intercepts arbitrary hosts, so the listener must present a valid leaf for whatever SNI the client requests.
Dynamic CA (src/mitm/tproxy/dynamicCert.ts)
DynamicCertStore runs a local CA (built on the selfsigned dependency) that:
- Generates a long-lived CA via
generateMitmCa()(CN"OmniRoute MITM CA", 10-year validity,basicConstraints CA=true+keyUsage keyCertSign,cRLSign, 2048-bit RSA / SHA-256). - Issues a leaf per SNI hostname on demand via
issueLeafCert()(1-year validity,subjectAltName= the SNI host) and caches onetls.SecureContextper hostname. - Exposes
createSNICallback()for the TLS-terminating server (see §5). - Can be constructed with an
existingCato keep the CA stable across restarts (so the trust store does not need re-installing).
The CA private key never leaves the machine.
Trust-store installer (src/mitm/tproxy/caTrust.ts)
The intercepted client must trust the dynamic CA, so starting the capture mode
installs the CA cert into the OS trust store under a dedicated slot —
omniroute-tproxy-ca.crt (constant TPROXY_CA_CERT_NAME) — kept separate from
the static MITM cert's slot (omniroute-mitm.crt) so the two never clobber each
other.
installTproxyCa(caPem, sudoPassword?) detects the distro's anchor directory
(in order: Debian-style first) and runs the matching refresh command:
| Anchor directory | Refresh command |
|---|---|
/usr/local/share/ca-certificates |
update-ca-certificates |
/etc/ca-certificates/trust-source/anchors |
update-ca-trust |
/etc/pki/ca-trust/source/anchors |
update-ca-trust |
/etc/pki/trust/anchors |
update-ca-certificates |
Install stages the PEM to a temp file, then (privileged) mkdir -p the anchor
dir, cp the staged file into it, and runs the refresh command. uninstallTproxyCa()
removes the dedicated slot only (leaving the static MITM cert untouched) and
refreshes — a no-op on non-Linux.
All privileged commands run via execFileWithPassword (src/mitm/systemCommands.ts)
— spawn with arg arrays, no shell, no string interpolation (Hard Rule #13).
When the process is root (e.g. the VPS) the target runs directly and no password
is needed; on a non-root desktop the sudoPassword is passed via sudo -S on stdin.
The desktop's
sudoPasswordis supplied in the POST body to authorize the trust-store install; it is ignored entirely when the process is root.
§5 How decrypt and capture work
The pipeline (all under src/mitm/tproxy/):
local app ──TCP/443──▶ mangle OUTPUT marks the conn (fwmark)
ip rule → local route table → lo
mangle PREROUTING TPROXY → IP_TRANSPARENT listener (port 8443)
│ captureMode.ts: reads orig dest from socket.localAddress
▼
tlsCapture.ts:
1. TLS-terminate the CLIENT with a per-SNI leaf (dynamicCert)
2. internal http.Server parses the decrypted plaintext
3. capture → globalTrafficBuffer.push() with source: "tproxy"
(sanitizeHeaders + maskSecret applied)
4. forward RE-encrypted to the original destination
over a bypass-marked socket (connectMarked, anti-loop)
│
▼
original upstream (api.example.com)
- TLS termination (
createTlsCaptureServer): wraps the raw intercepted socket in a server-sidetls.TLSSocketusing the dynamic CA's SNI callback, then hands the decrypted stream to an internalhttp.Server(the standard MITM termination trick). Socket lifetimes are bounded byMITM_IDLE_TIMEOUT_MSso a hung tunnel cannot exhaust file descriptors. - Capture (
handleDecryptedRequest): pushes anInterceptedRequestwithsource: "tproxy", status starting"in-flight", headers run throughsanitizeHeaders()and bodies throughmaskSecret()before they enter the buffer. The entry is then updated with the response, sizes, and latency. - Re-encrypted forward (
createForward/realForward): re-encrypts to the original destination.rejectUnauthorizeddefaults totrue(secure by default) — the upstream cert is verified against the SNI/Host the client requested, so the proxy rejects exactly what the original client would.
Anti-loop (SO_MARK)
Because the rules mark new local outbound connections, the proxy's own re-encrypted forward would normally be re-intercepted — an infinite loop. The forward path defends against this with a bypass socket mark (SO_MARK):
realForwardopens its upstream socket viaconnectMarked(ip, port, DEFAULT_BYPASS_MARK)—DEFAULT_BYPASS_MARK = 0x539— which sets the SO_MARK beforeconnect(), so the forward's SYN carries the bypass mark.- The
mangle OUTPUTrule excludes connections already carrying the bypass mark (-m mark ! --mark <bypassMark>), so the proxy's forward is not re-marked and does not re-enter TPROXY.
Implementation note: the bypass-marked socket must be installed on the agent's
createConnection(https.request({ createConnection })is silently ignored when an agent is present), or the forward would open an unmarked socket and the loop would return. This was the e2e-validated anti-loop fix.
§6 Security
| Control | Detail |
|---|---|
| Loopback-only API | /api/tools/agent-bridge/tproxy is covered by the /api/tools/agent-bridge/ prefix in LOCAL_ONLY_API_PREFIXES (src/server/authz/routeGuard.ts). Loopback enforcement runs before auth (Hard Rules #15 + #17) — a leaked JWT over a tunnel cannot start TPROXY capture, which applies iptables rules and installs a trust-store CA via child processes. |
| Dedicated CA slot | The dynamic CA installs to omniroute-tproxy-ca.crt, never clobbering the static MITM cert. |
| CA key never leaves the host | DynamicCertStore holds the CA key in memory; it is not exported. |
| Secret masking | maskSecret() on request/response bodies and sanitizeHeaders() on headers run before globalTrafficBuffer.push(). |
| No shell interpolation | All iptables/ip/trust-store commands run via execFile/execFileWithPassword with arg arrays (Hard Rule #13). |
| Upstream cert verification | The re-encrypted forward verifies the upstream cert by default (rejectUnauthorized: true). |
| Error sanitization | The route's error responses go through sanitizeErrorMessage() (Hard Rule #12). |
The MITM CA is a powerful capability. A CA trusted by the OS that can sign any host means anything OmniRoute intercepts can be decrypted. It is gated behind the explicit, local-only TPROXY capture mode, off by default, and the trust-store entry is removed when you stop the mode.
§7 Transactional firewall apply / revert
A crash must never leave a mangle rule or stale route behind. The command builder
(src/mitm/tproxy/commands.ts) and runner (src/mitm/tproxy/setup.ts) guarantee
revert is the exact inverse of apply, in reverse order.
applyTproxy(cfg) runs the apply commands in order; on any failure it runs a
best-effort full revertTproxy(cfg) and rethrows — so the firewall is either
fully applied or fully reverted, never half-applied. revertTproxy(cfg) runs the
inverse commands in reverse order and swallows failures (idempotent — safe to call
unconditionally, e.g. from the AgentBridge repairMitm() cleanup).
validateTproxyConfig(cfg) runs before any command: ports must be 1–65535,
mark/routeTable/bypassMark must be positive integers, and bypassMark must
differ from mark (anti-loop).
Apply commands (in order)
ip rule add fwmark <mark> lookup <routeTable>
ip route add local 0.0.0.0/0 dev lo table <routeTable>
iptables -t mangle -A OUTPUT -p tcp --dport <dport> -m mark ! --mark <bypassMark> -j MARK --set-mark <mark>
iptables -t mangle -A PREROUTING -p tcp --dport <dport> -m mark --mark <mark> -j TPROXY --on-port <onPort> --tproxy-mark <mark>
Revert deletes them in reverse: PREROUTING -D, OUTPUT -D, ip route del, ip rule del.
The recipe is OUTPUT-based because the MITM use case is local outbound traffic (apps on the same host), which TPROXY in
PREROUTINGalone does not see —PREROUTINGonly sees forwarded traffic. TheOUTPUTchain marks new local connections, theip rulereroutes them to local delivery (lo), andPREROUTINGthen assigns them to the transparent listener.
§8 Configuration
The start request (POST /api/tools/agent-bridge/tproxy) accepts the following
fields, validated by StartTproxyBodySchema (tproxy/route.ts). All are optional
and fall back to their defaults:
| Field | Type | Default | Notes |
|---|---|---|---|
| dport | int (1–65535) | 443 |
Destination TCP port to transparently intercept |
| mark | int (≥1) | 0x2333 |
Firewall mark set on OUTPUT, matched by the ip rule + PREROUTING |
| onPort | int (1–65535) | 8443 |
Port the transparent (IP_TRANSPARENT) listener binds |
| routeTable | int (≥1) | 233 |
Policy-routing table id holding the local 0.0.0.0/0 route |
| bypassMark | int (≥1, ≠ mark) |
0x539 |
The bypass socket mark (SO_MARK) the proxy sets on its own upstream conns; excluded in OUTPUT (anti-loop) |
| sudoPassword | string | — | Non-root desktops only: authorizes the trust-store install; ignored when root |
There are no environment variables for TPROXY — all configuration is via the POST body or the defaults above.
§9 Enabling from the Traffic Inspector
- Open the Traffic Inspector (
/dashboard/tools/traffic-inspector). - In the capture-modes toolbar, find the "TPROXY Decrypt" ⚠ button
(
src/app/(dashboard)/dashboard/tools/traffic-inspector/components/CaptureModesToolbar.tsx). - Click the button. It calls
POST /api/tools/agent-bridge/tproxyviastartTproxyCaptureMode()(src/lib/inspector/tproxyCaptureApi.ts), which: builds the dynamic CA, opens the transparent listener, applies the firewall rules, and installs the CA in the OS trust store. - When running, the toggle turns amber and shows the live intercept count
(
· <interceptCount>). Intercepted requests appear in the request list withsource: "tproxy". - Click again to stop —
DELETE /api/tools/agent-bridge/tproxyviastopTproxyCaptureMode()closes the listener, uninstalls the CA, and reverts the firewall rules.
The capture-mode status (running / available / intercept count / listener port) comes
from GET /api/tools/agent-bridge/tproxy (getCaptureStatus() in
src/mitm/tproxy/captureManager.ts). Only one TPROXY session runs at a time —
starting a second rejects with "TPROXY capture mode is already running".
§10 Troubleshooting
Toggle is disabled
The native addon is not loadable. Confirm: you are on Linux, you built the addon
(npm run build:native:tproxy), and the process can load transparent.node.
isTransparentSocketAvailable() gates the toggle; GET /api/tools/agent-bridge/tproxy
returns available: false when the addon is missing.
Nothing is captured
- Confirm the intercepted process actually connects to the configured
dport(default443). - Confirm the process trusts the dynamic CA. The CA is installed under
omniroute-tproxy-ca.crt; apps with their own trust store (Firefox/Chrome NSS) may need the cert added there too. - Run the AgentBridge Diagnose self-test (see
AGENTBRIDGE.md) for cert-trusted / server health checks.
Stale firewall rules after a crash
revertTproxy() is the exact inverse of apply and is idempotent. Stopping the
mode reverts the rules; if OmniRoute was killed mid-session, use the AgentBridge
Repair action (POST /api/tools/agent-bridge/repair) to undo orphaned system
state (DNS spoof, root CA, system proxy). The TPROXY mangle rules and route also
flush automatically on reboot.
Infinite loop / the proxy intercepts its own forward
This is the anti-loop case. Confirm bypassMark differs from mark (validation
enforces this) and that the forward uses connectMarked (it does in realForward).
See §5 Anti-loop.
§11 Source map
| File | Responsibility |
|---|---|
src/mitm/tproxy/commands.ts |
Pure iptables/ip apply + revert command builder; validateTproxyConfig |
src/mitm/tproxy/setup.ts |
Transactional applyTproxy / revertTproxy runner (rollback on failure) |
src/mitm/tproxy/transparentSocket.ts |
Native-addon loader (loadTransparentAddon), createTransparentListenerFd, connectMarked, setSocketMark, isTransparentSocketAvailable |
src/mitm/tproxy/native/transparent.c |
N-API addon: createTransparentListener (IP_TRANSPARENT), setSocketMark, connectMarked |
src/mitm/tproxy/native/binding.gyp |
node-gyp build manifest |
src/mitm/tproxy/dynamicCert.ts |
DynamicCertStore — per-SNI dynamic CA + leaf cache |
src/mitm/tproxy/caTrust.ts |
OS trust-store install/uninstall (installTproxyCa / uninstallTproxyCa, dedicated slot) |
src/mitm/tproxy/tlsCapture.ts |
TLS-terminating decrypt engine + re-encrypted anti-loop forward |
src/mitm/tproxy/captureMode.ts |
Transparent-listener orchestration; reads orig dest from socket.localAddress |
src/mitm/tproxy/captureManager.ts |
Singleton lifecycle: startCaptureMode / stopCaptureMode / getCaptureStatus |
src/app/api/tools/agent-bridge/tproxy/route.ts |
GET / POST / DELETE route (LOCAL_ONLY) |
src/lib/inspector/tproxyCaptureApi.ts |
Client fetch helpers (fetchTproxyStatus / startTproxyCaptureMode / stopTproxyCaptureMode) |