diff --git a/.agents/skills/review-discussions-ag/SKILL.md b/.agents/skills/review-discussions-ag/SKILL.md index c7e5c2d380..2f16d23c37 100644 --- a/.agents/skills/review-discussions-ag/SKILL.md +++ b/.agents/skills/review-discussions-ag/SKILL.md @@ -25,11 +25,15 @@ This workflow reads all open GitHub Discussions, generates a categorized summary - Run: `git -C remote get-url origin` to extract `owner/repo`. - Parse owner and repo name from the URL (https or ssh form). -### 2. Fetch All Open Discussions (single GraphQL query) +### 2. Fetch All Open Discussions (paginated GraphQL) -Single `gh api graphql` call — return everything needed for triage. Critical fields: `id` (node ID, **not** the visible `number`), `number`, `title`, `url`, `createdAt`, `updatedAt`, `author.login`, `category.name`, `body`, `answerChosenAt`, plus nested `comments(first: 50) { totalCount, nodes { id, author.login, body, createdAt, replies(first: 20) { nodes { author.login, body, createdAt } } } }`. +GraphQL caps each `discussions` query at 50 nodes — repos with more than 50 open discussions **must paginate**. Loop with `first: 50, after: $cursor` until `pageInfo.hasNextPage` is `false`. Skipping pagination silently drops the older half of the backlog, which is exactly where most stale-candidates and unanswered follow-ups live (regression observed 2026-05-28: page-1-only fetch missed 5 follow-ups and 4 stale candidates ranging from 23d to 56d). -Persist the raw JSON to `/tmp/discussions--.json` so re-runs in the same session avoid a re-fetch. Build an `id → number` map for the post phase — the GraphQL `addDiscussionComment` mutation requires the node ID, not the number. +Each page request must return the **same field set** — easy mistake is to fetch page 2 without `body` (because the cursor query was hand-edited). Define one query string with `body` on both the discussion and every comment/reply, and reuse it across pages. + +Critical fields per discussion: `id` (node ID, **not** the visible `number`), `number`, `title`, `url`, `createdAt`, `updatedAt`, `author.login`, `category.name`, `body`, `answerChosenAt`, `labels(first: 10) { nodes { name } }`, plus nested `comments(first: 50) { totalCount, nodes { id, author.login, body, createdAt, replies(first: 20) { nodes { author.login, body, createdAt } } } }`. Must also include `pageInfo { hasNextPage endCursor }` on the discussions connection. + +Persist the **merged** result (all pages concatenated) to `/tmp/discussions--.json` so re-runs in the same session avoid a re-fetch. Build an `id → number` map for the post phase — the GraphQL `addDiscussionComment` mutation requires the node ID, not the number. Capture **image attachments** present in body or comments (`` or markdown `![...](...)`). Surface their count in the per-discussion summary (e.g., `📷 3 screenshots`) so the user can decide if visual context matters before approving a draft. diff --git a/.agents/skills/review-discussions-cc/SKILL.md b/.agents/skills/review-discussions-cc/SKILL.md index 17bded82e5..722dd4153d 100644 --- a/.agents/skills/review-discussions-cc/SKILL.md +++ b/.agents/skills/review-discussions-cc/SKILL.md @@ -25,11 +25,15 @@ This workflow reads all open GitHub Discussions, generates a categorized summary - Run: `git -C remote get-url origin` to extract `owner/repo`. - Parse owner and repo name from the URL (https or ssh form). -### 2. Fetch All Open Discussions (single GraphQL query) +### 2. Fetch All Open Discussions (paginated GraphQL) -Single `gh api graphql` call — return everything needed for triage. Critical fields: `id` (node ID, **not** the visible `number`), `number`, `title`, `url`, `createdAt`, `updatedAt`, `author.login`, `category.name`, `body`, `answerChosenAt`, plus nested `comments(first: 50) { totalCount, nodes { id, author.login, body, createdAt, replies(first: 20) { nodes { author.login, body, createdAt } } } }`. +GraphQL caps each `discussions` query at 50 nodes — repos with more than 50 open discussions **must paginate**. Loop with `first: 50, after: $cursor` until `pageInfo.hasNextPage` is `false`. Skipping pagination silently drops the older half of the backlog, which is exactly where most stale-candidates and unanswered follow-ups live (regression observed 2026-05-28: page-1-only fetch missed 5 follow-ups and 4 stale candidates ranging from 23d to 56d). -Persist the raw JSON to `/tmp/discussions--.json` so re-runs in the same session avoid a re-fetch. Build an `id → number` map for the post phase — the GraphQL `addDiscussionComment` mutation requires the node ID, not the number. +Each page request must return the **same field set** — easy mistake is to fetch page 2 without `body` (because the cursor query was hand-edited). Define one query string with `body` on both the discussion and every comment/reply, and reuse it across pages. + +Critical fields per discussion: `id` (node ID, **not** the visible `number`), `number`, `title`, `url`, `createdAt`, `updatedAt`, `author.login`, `category.name`, `body`, `answerChosenAt`, `labels(first: 10) { nodes { name } }`, plus nested `comments(first: 50) { totalCount, nodes { id, author.login, body, createdAt, replies(first: 20) { nodes { author.login, body, createdAt } } } }`. Must also include `pageInfo { hasNextPage endCursor }` on the discussions connection. + +Persist the **merged** result (all pages concatenated) to `/tmp/discussions--.json` so re-runs in the same session avoid a re-fetch. Build an `id → number` map for the post phase — the GraphQL `addDiscussionComment` mutation requires the node ID, not the number. Capture **image attachments** present in body or comments (`` or markdown `![...](...)`). Surface their count in the per-discussion summary (e.g., `📷 3 screenshots`) so the user can decide if visual context matters before approving a draft. diff --git a/.agents/skills/review-discussions-cx/SKILL.md b/.agents/skills/review-discussions-cx/SKILL.md index c417c18755..e47ba861c5 100644 --- a/.agents/skills/review-discussions-cx/SKILL.md +++ b/.agents/skills/review-discussions-cx/SKILL.md @@ -32,11 +32,15 @@ This workflow reads all open GitHub Discussions, generates a categorized summary - Run: `git -C remote get-url origin` to extract `owner/repo`. - Parse owner and repo name from the URL (https or ssh form). -### 2. Fetch All Open Discussions (single GraphQL query) +### 2. Fetch All Open Discussions (paginated GraphQL) -Single `gh api graphql` call — return everything needed for triage. Critical fields: `id` (node ID, **not** the visible `number`), `number`, `title`, `url`, `createdAt`, `updatedAt`, `author.login`, `category.name`, `body`, `answerChosenAt`, plus nested `comments(first: 50) { totalCount, nodes { id, author.login, body, createdAt, replies(first: 20) { nodes { author.login, body, createdAt } } } }`. +GraphQL caps each `discussions` query at 50 nodes — repos with more than 50 open discussions **must paginate**. Loop with `first: 50, after: $cursor` until `pageInfo.hasNextPage` is `false`. Skipping pagination silently drops the older half of the backlog, which is exactly where most stale-candidates and unanswered follow-ups live (regression observed 2026-05-28: page-1-only fetch missed 5 follow-ups and 4 stale candidates ranging from 23d to 56d). -Persist the raw JSON to `/tmp/discussions--.json` so re-runs in the same session avoid a re-fetch. Build an `id → number` map for the post phase — the GraphQL `addDiscussionComment` mutation requires the node ID, not the number. +Each page request must return the **same field set** — easy mistake is to fetch page 2 without `body` (because the cursor query was hand-edited). Define one query string with `body` on both the discussion and every comment/reply, and reuse it across pages. + +Critical fields per discussion: `id` (node ID, **not** the visible `number`), `number`, `title`, `url`, `createdAt`, `updatedAt`, `author.login`, `category.name`, `body`, `answerChosenAt`, `labels(first: 10) { nodes { name } }`, plus nested `comments(first: 50) { totalCount, nodes { id, author.login, body, createdAt, replies(first: 20) { nodes { author.login, body, createdAt } } } }`. Must also include `pageInfo { hasNextPage endCursor }` on the discussions connection. + +Persist the **merged** result (all pages concatenated) to `/tmp/discussions--.json` so re-runs in the same session avoid a re-fetch. Build an `id → number` map for the post phase — the GraphQL `addDiscussionComment` mutation requires the node ID, not the number. Capture **image attachments** present in body or comments (`` or markdown `![...](...)`). Surface their count in the per-discussion summary (e.g., `📷 3 screenshots`) so the user can decide if visual context matters before approving a draft. diff --git a/.env.example b/.env.example index d2dd21759e..69aad21c34 100644 --- a/.env.example +++ b/.env.example @@ -1345,6 +1345,21 @@ APP_LOG_TO_FILE=true # ELECTRON_SMOKE_KEEP_DATA=0 # ELECTRON_SMOKE_STREAM_LOGS=0 +# AgentBridge + Traffic Inspector (Group A) + +# AgentBridge +AGENTBRIDGE_UPSTREAM_CA_CERT= + +# Inspector +INSPECTOR_BUFFER_SIZE=1000 +INSPECTOR_HTTP_PROXY_PORT=8080 +INSPECTOR_HTTP_PROXY_AUTOSTART=false +INSPECTOR_TLS_INTERCEPT=false +INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES=30 +INSPECTOR_MAX_BODY_KB=1024 +INSPECTOR_MASK_SECRETS=true +INSPECTOR_LLM_HOSTS_EXTRA= +INSPECTOR_INTERNAL_INGEST_TOKEN= # Quota Sharing (Group B — planos 16+22) QUOTA_STORE_DRIVER=sqlite # sqlite | redis # QUOTA_STORE_REDIS_URL= # ex.: redis://localhost:6379 (apenas quando driver=redis) diff --git a/.source/browser.ts b/.source/browser.ts index ce5576f0bb..b8208d2690 100644 --- a/.source/browser.ts +++ b/.source/browser.ts @@ -7,6 +7,6 @@ const create = browser(); const browserCollections = { - docs: create.doc("docs", {"architecture/ARCHITECTURE.md": () => import("../docs/architecture/ARCHITECTURE.md?collection=docs"), "architecture/AUTHZ_GUIDE.md": () => import("../docs/architecture/AUTHZ_GUIDE.md?collection=docs"), "architecture/CODEBASE_DOCUMENTATION.md": () => import("../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"), "architecture/REPOSITORY_MAP.md": () => import("../docs/architecture/REPOSITORY_MAP.md?collection=docs"), "architecture/RESILIENCE_GUIDE.md": () => import("../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"), "compression/COMPRESSION_ENGINES.md": () => import("../docs/compression/COMPRESSION_ENGINES.md?collection=docs"), "compression/COMPRESSION_GUIDE.md": () => import("../docs/compression/COMPRESSION_GUIDE.md?collection=docs"), "compression/COMPRESSION_LANGUAGE_PACKS.md": () => import("../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"), "compression/COMPRESSION_RULES_FORMAT.md": () => import("../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"), "compression/RTK_COMPRESSION.md": () => import("../docs/compression/RTK_COMPRESSION.md?collection=docs"), "guides/DOCKER_GUIDE.md": () => import("../docs/guides/DOCKER_GUIDE.md?collection=docs"), "guides/ELECTRON_GUIDE.md": () => import("../docs/guides/ELECTRON_GUIDE.md?collection=docs"), "guides/FEATURES.md": () => import("../docs/guides/FEATURES.md?collection=docs"), "guides/I18N.md": () => import("../docs/guides/I18N.md?collection=docs"), "guides/KIRO_SETUP.md": () => import("../docs/guides/KIRO_SETUP.md?collection=docs"), "guides/PWA_GUIDE.md": () => import("../docs/guides/PWA_GUIDE.md?collection=docs"), "guides/SETUP_GUIDE.md": () => import("../docs/guides/SETUP_GUIDE.md?collection=docs"), "guides/TERMUX_GUIDE.md": () => import("../docs/guides/TERMUX_GUIDE.md?collection=docs"), "guides/TROUBLESHOOTING.md": () => import("../docs/guides/TROUBLESHOOTING.md?collection=docs"), "guides/UNINSTALL.md": () => import("../docs/guides/UNINSTALL.md?collection=docs"), "guides/USER_GUIDE.md": () => import("../docs/guides/USER_GUIDE.md?collection=docs"), "frameworks/A2A-SERVER.md": () => import("../docs/frameworks/A2A-SERVER.md?collection=docs"), "frameworks/AGENT_PROTOCOLS_GUIDE.md": () => import("../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"), "frameworks/CLOUD_AGENT.md": () => import("../docs/frameworks/CLOUD_AGENT.md?collection=docs"), "frameworks/EMBEDDED-SERVICES.md": () => import("../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"), "frameworks/EVALS.md": () => import("../docs/frameworks/EVALS.md?collection=docs"), "frameworks/GAMIFICATION.md": () => import("../docs/frameworks/GAMIFICATION.md?collection=docs"), "frameworks/MCP-SERVER.md": () => import("../docs/frameworks/MCP-SERVER.md?collection=docs"), "frameworks/MEMORY.md": () => import("../docs/frameworks/MEMORY.md?collection=docs"), "frameworks/OPENCODE.md": () => import("../docs/frameworks/OPENCODE.md?collection=docs"), "frameworks/SKILLS.md": () => import("../docs/frameworks/SKILLS.md?collection=docs"), "frameworks/WEBHOOKS.md": () => import("../docs/frameworks/WEBHOOKS.md?collection=docs"), "ops/COVERAGE_PLAN.md": () => import("../docs/ops/COVERAGE_PLAN.md?collection=docs"), "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": () => import("../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"), "ops/FLY_IO_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"), "ops/PROXY_GUIDE.md": () => import("../docs/ops/PROXY_GUIDE.md?collection=docs"), "ops/RELEASE_CHECKLIST.md": () => import("../docs/ops/RELEASE_CHECKLIST.md?collection=docs"), "ops/SQLITE_RUNTIME.md": () => import("../docs/ops/SQLITE_RUNTIME.md?collection=docs"), "ops/TUNNELS_GUIDE.md": () => import("../docs/ops/TUNNELS_GUIDE.md?collection=docs"), "ops/VM_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"), "reference/API_REFERENCE.md": () => import("../docs/reference/API_REFERENCE.md?collection=docs"), "reference/CLI-TOOLS.md": () => import("../docs/reference/CLI-TOOLS.md?collection=docs"), "reference/ENVIRONMENT.md": () => import("../docs/reference/ENVIRONMENT.md?collection=docs"), "reference/FREE_TIERS.md": () => import("../docs/reference/FREE_TIERS.md?collection=docs"), "reference/PROVIDER_REFERENCE.md": () => import("../docs/reference/PROVIDER_REFERENCE.md?collection=docs"), "routing/AUTO-COMBO.md": () => import("../docs/routing/AUTO-COMBO.md?collection=docs"), "routing/REASONING_REPLAY.md": () => import("../docs/routing/REASONING_REPLAY.md?collection=docs"), "security/CLI_TOKEN.md": () => import("../docs/security/CLI_TOKEN.md?collection=docs"), "security/CLI_TOKEN_AUTH.md": () => import("../docs/security/CLI_TOKEN_AUTH.md?collection=docs"), "security/COMPLIANCE.md": () => import("../docs/security/COMPLIANCE.md?collection=docs"), "security/ERROR_SANITIZATION.md": () => import("../docs/security/ERROR_SANITIZATION.md?collection=docs"), "security/GUARDRAILS.md": () => import("../docs/security/GUARDRAILS.md?collection=docs"), "security/PUBLIC_CREDS.md": () => import("../docs/security/PUBLIC_CREDS.md?collection=docs"), "security/ROUTE_GUARD_TIERS.md": () => import("../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"), "security/SOCKET_DEV_FINDINGS.md": () => import("../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs"), "security/STEALTH_GUIDE.md": () => import("../docs/security/STEALTH_GUIDE.md?collection=docs"), }), + docs: create.doc("docs", {"architecture/ARCHITECTURE.md": () => import("../docs/architecture/ARCHITECTURE.md?collection=docs"), "architecture/AUTHZ_GUIDE.md": () => import("../docs/architecture/AUTHZ_GUIDE.md?collection=docs"), "architecture/CODEBASE_DOCUMENTATION.md": () => import("../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs"), "architecture/MONITORING_SECTIONS.md": () => import("../docs/architecture/MONITORING_SECTIONS.md?collection=docs"), "architecture/REPOSITORY_MAP.md": () => import("../docs/architecture/REPOSITORY_MAP.md?collection=docs"), "architecture/RESILIENCE_GUIDE.md": () => import("../docs/architecture/RESILIENCE_GUIDE.md?collection=docs"), "compression/COMPRESSION_ENGINES.md": () => import("../docs/compression/COMPRESSION_ENGINES.md?collection=docs"), "compression/COMPRESSION_GUIDE.md": () => import("../docs/compression/COMPRESSION_GUIDE.md?collection=docs"), "compression/COMPRESSION_LANGUAGE_PACKS.md": () => import("../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"), "compression/COMPRESSION_RULES_FORMAT.md": () => import("../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"), "compression/RTK_COMPRESSION.md": () => import("../docs/compression/RTK_COMPRESSION.md?collection=docs"), "frameworks/A2A-SERVER.md": () => import("../docs/frameworks/A2A-SERVER.md?collection=docs"), "frameworks/AGENTBRIDGE.md": () => import("../docs/frameworks/AGENTBRIDGE.md?collection=docs"), "frameworks/AGENT_PROTOCOLS_GUIDE.md": () => import("../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"), "frameworks/CLOUD_AGENT.md": () => import("../docs/frameworks/CLOUD_AGENT.md?collection=docs"), "frameworks/EMBEDDED-SERVICES.md": () => import("../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"), "frameworks/EVALS.md": () => import("../docs/frameworks/EVALS.md?collection=docs"), "frameworks/GAMIFICATION.md": () => import("../docs/frameworks/GAMIFICATION.md?collection=docs"), "frameworks/MCP-SERVER.md": () => import("../docs/frameworks/MCP-SERVER.md?collection=docs"), "frameworks/MEMORY.md": () => import("../docs/frameworks/MEMORY.md?collection=docs"), "frameworks/OPENCODE.md": () => import("../docs/frameworks/OPENCODE.md?collection=docs"), "frameworks/SKILLS.md": () => import("../docs/frameworks/SKILLS.md?collection=docs"), "frameworks/TRAFFIC_INSPECTOR.md": () => import("../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs"), "frameworks/WEBHOOKS.md": () => import("../docs/frameworks/WEBHOOKS.md?collection=docs"), "guides/DOCKER_GUIDE.md": () => import("../docs/guides/DOCKER_GUIDE.md?collection=docs"), "guides/ELECTRON_GUIDE.md": () => import("../docs/guides/ELECTRON_GUIDE.md?collection=docs"), "guides/FEATURES.md": () => import("../docs/guides/FEATURES.md?collection=docs"), "guides/I18N.md": () => import("../docs/guides/I18N.md?collection=docs"), "guides/KIRO_SETUP.md": () => import("../docs/guides/KIRO_SETUP.md?collection=docs"), "guides/PWA_GUIDE.md": () => import("../docs/guides/PWA_GUIDE.md?collection=docs"), "guides/SETUP_GUIDE.md": () => import("../docs/guides/SETUP_GUIDE.md?collection=docs"), "guides/TERMUX_GUIDE.md": () => import("../docs/guides/TERMUX_GUIDE.md?collection=docs"), "guides/TROUBLESHOOTING.md": () => import("../docs/guides/TROUBLESHOOTING.md?collection=docs"), "guides/UNINSTALL.md": () => import("../docs/guides/UNINSTALL.md?collection=docs"), "guides/USER_GUIDE.md": () => import("../docs/guides/USER_GUIDE.md?collection=docs"), "ops/COVERAGE_PLAN.md": () => import("../docs/ops/COVERAGE_PLAN.md?collection=docs"), "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": () => import("../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"), "ops/FLY_IO_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"), "ops/PROXY_GUIDE.md": () => import("../docs/ops/PROXY_GUIDE.md?collection=docs"), "ops/RELEASE_CHECKLIST.md": () => import("../docs/ops/RELEASE_CHECKLIST.md?collection=docs"), "ops/SQLITE_RUNTIME.md": () => import("../docs/ops/SQLITE_RUNTIME.md?collection=docs"), "ops/TUNNELS_GUIDE.md": () => import("../docs/ops/TUNNELS_GUIDE.md?collection=docs"), "ops/VM_DEPLOYMENT_GUIDE.md": () => import("../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs"), "reference/API_REFERENCE.md": () => import("../docs/reference/API_REFERENCE.md?collection=docs"), "reference/CLI-TOOLS.md": () => import("../docs/reference/CLI-TOOLS.md?collection=docs"), "reference/ENVIRONMENT.md": () => import("../docs/reference/ENVIRONMENT.md?collection=docs"), "reference/FREE_TIERS.md": () => import("../docs/reference/FREE_TIERS.md?collection=docs"), "reference/PROVIDER_REFERENCE.md": () => import("../docs/reference/PROVIDER_REFERENCE.md?collection=docs"), "routing/AUTO-COMBO.md": () => import("../docs/routing/AUTO-COMBO.md?collection=docs"), "routing/QUOTA_SHARE.md": () => import("../docs/routing/QUOTA_SHARE.md?collection=docs"), "routing/REASONING_REPLAY.md": () => import("../docs/routing/REASONING_REPLAY.md?collection=docs"), "security/CLI_TOKEN.md": () => import("../docs/security/CLI_TOKEN.md?collection=docs"), "security/CLI_TOKEN_AUTH.md": () => import("../docs/security/CLI_TOKEN_AUTH.md?collection=docs"), "security/COMPLIANCE.md": () => import("../docs/security/COMPLIANCE.md?collection=docs"), "security/ERROR_SANITIZATION.md": () => import("../docs/security/ERROR_SANITIZATION.md?collection=docs"), "security/GUARDRAILS.md": () => import("../docs/security/GUARDRAILS.md?collection=docs"), "security/PUBLIC_CREDS.md": () => import("../docs/security/PUBLIC_CREDS.md?collection=docs"), "security/ROUTE_GUARD_TIERS.md": () => import("../docs/security/ROUTE_GUARD_TIERS.md?collection=docs"), "security/SOCKET_DEV_FINDINGS.md": () => import("../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs"), "security/STEALTH_GUIDE.md": () => import("../docs/security/STEALTH_GUIDE.md?collection=docs"), }), }; export default browserCollections; \ No newline at end of file diff --git a/.source/server.ts b/.source/server.ts index b7e781f56b..6204df0fef 100644 --- a/.source/server.ts +++ b/.source/server.ts @@ -1,67 +1,71 @@ // @ts-nocheck -import { default as __fd_glob_65 } from "../docs/security/meta.json?collection=docs" -import { default as __fd_glob_64 } from "../docs/routing/meta.json?collection=docs" -import { default as __fd_glob_63 } from "../docs/reference/openapi.yaml?collection=docs" -import { default as __fd_glob_62 } from "../docs/reference/meta.json?collection=docs" -import { default as __fd_glob_61 } from "../docs/ops/meta.json?collection=docs" -import { default as __fd_glob_60 } from "../docs/guides/meta.json?collection=docs" -import { default as __fd_glob_59 } from "../docs/frameworks/meta.json?collection=docs" -import { default as __fd_glob_58 } from "../docs/compression/meta.json?collection=docs" -import { default as __fd_glob_57 } from "../docs/architecture/meta.json?collection=docs" -import { default as __fd_glob_56 } from "../docs/meta.json?collection=docs" -import * as __fd_glob_55 from "../docs/security/STEALTH_GUIDE.md?collection=docs" -import * as __fd_glob_54 from "../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs" -import * as __fd_glob_53 from "../docs/security/ROUTE_GUARD_TIERS.md?collection=docs" -import * as __fd_glob_52 from "../docs/security/PUBLIC_CREDS.md?collection=docs" -import * as __fd_glob_51 from "../docs/security/GUARDRAILS.md?collection=docs" -import * as __fd_glob_50 from "../docs/security/ERROR_SANITIZATION.md?collection=docs" -import * as __fd_glob_49 from "../docs/security/COMPLIANCE.md?collection=docs" -import * as __fd_glob_48 from "../docs/security/CLI_TOKEN_AUTH.md?collection=docs" -import * as __fd_glob_47 from "../docs/security/CLI_TOKEN.md?collection=docs" -import * as __fd_glob_46 from "../docs/routing/REASONING_REPLAY.md?collection=docs" -import * as __fd_glob_45 from "../docs/routing/AUTO-COMBO.md?collection=docs" -import * as __fd_glob_44 from "../docs/reference/PROVIDER_REFERENCE.md?collection=docs" -import * as __fd_glob_43 from "../docs/reference/FREE_TIERS.md?collection=docs" -import * as __fd_glob_42 from "../docs/reference/ENVIRONMENT.md?collection=docs" -import * as __fd_glob_41 from "../docs/reference/CLI-TOOLS.md?collection=docs" -import * as __fd_glob_40 from "../docs/reference/API_REFERENCE.md?collection=docs" -import * as __fd_glob_39 from "../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs" -import * as __fd_glob_38 from "../docs/ops/TUNNELS_GUIDE.md?collection=docs" -import * as __fd_glob_37 from "../docs/ops/SQLITE_RUNTIME.md?collection=docs" -import * as __fd_glob_36 from "../docs/ops/RELEASE_CHECKLIST.md?collection=docs" -import * as __fd_glob_35 from "../docs/ops/PROXY_GUIDE.md?collection=docs" -import * as __fd_glob_34 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs" -import * as __fd_glob_33 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs" -import * as __fd_glob_32 from "../docs/ops/COVERAGE_PLAN.md?collection=docs" -import * as __fd_glob_31 from "../docs/frameworks/WEBHOOKS.md?collection=docs" -import * as __fd_glob_30 from "../docs/frameworks/SKILLS.md?collection=docs" -import * as __fd_glob_29 from "../docs/frameworks/OPENCODE.md?collection=docs" -import * as __fd_glob_28 from "../docs/frameworks/MEMORY.md?collection=docs" -import * as __fd_glob_27 from "../docs/frameworks/MCP-SERVER.md?collection=docs" -import * as __fd_glob_26 from "../docs/frameworks/GAMIFICATION.md?collection=docs" -import * as __fd_glob_25 from "../docs/frameworks/EVALS.md?collection=docs" -import * as __fd_glob_24 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs" -import * as __fd_glob_23 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs" -import * as __fd_glob_22 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs" -import * as __fd_glob_21 from "../docs/frameworks/A2A-SERVER.md?collection=docs" -import * as __fd_glob_20 from "../docs/guides/USER_GUIDE.md?collection=docs" -import * as __fd_glob_19 from "../docs/guides/UNINSTALL.md?collection=docs" -import * as __fd_glob_18 from "../docs/guides/TROUBLESHOOTING.md?collection=docs" -import * as __fd_glob_17 from "../docs/guides/TERMUX_GUIDE.md?collection=docs" -import * as __fd_glob_16 from "../docs/guides/SETUP_GUIDE.md?collection=docs" -import * as __fd_glob_15 from "../docs/guides/PWA_GUIDE.md?collection=docs" -import * as __fd_glob_14 from "../docs/guides/KIRO_SETUP.md?collection=docs" -import * as __fd_glob_13 from "../docs/guides/I18N.md?collection=docs" -import * as __fd_glob_12 from "../docs/guides/FEATURES.md?collection=docs" -import * as __fd_glob_11 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs" -import * as __fd_glob_10 from "../docs/guides/DOCKER_GUIDE.md?collection=docs" -import * as __fd_glob_9 from "../docs/compression/RTK_COMPRESSION.md?collection=docs" -import * as __fd_glob_8 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs" -import * as __fd_glob_7 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs" -import * as __fd_glob_6 from "../docs/compression/COMPRESSION_GUIDE.md?collection=docs" -import * as __fd_glob_5 from "../docs/compression/COMPRESSION_ENGINES.md?collection=docs" -import * as __fd_glob_4 from "../docs/architecture/RESILIENCE_GUIDE.md?collection=docs" -import * as __fd_glob_3 from "../docs/architecture/REPOSITORY_MAP.md?collection=docs" +import { default as __fd_glob_69 } from "../docs/security/meta.json?collection=docs" +import { default as __fd_glob_68 } from "../docs/routing/meta.json?collection=docs" +import { default as __fd_glob_67 } from "../docs/reference/openapi.yaml?collection=docs" +import { default as __fd_glob_66 } from "../docs/reference/meta.json?collection=docs" +import { default as __fd_glob_65 } from "../docs/ops/meta.json?collection=docs" +import { default as __fd_glob_64 } from "../docs/guides/meta.json?collection=docs" +import { default as __fd_glob_63 } from "../docs/frameworks/meta.json?collection=docs" +import { default as __fd_glob_62 } from "../docs/compression/meta.json?collection=docs" +import { default as __fd_glob_61 } from "../docs/architecture/meta.json?collection=docs" +import { default as __fd_glob_60 } from "../docs/meta.json?collection=docs" +import * as __fd_glob_59 from "../docs/security/STEALTH_GUIDE.md?collection=docs" +import * as __fd_glob_58 from "../docs/security/SOCKET_DEV_FINDINGS.md?collection=docs" +import * as __fd_glob_57 from "../docs/security/ROUTE_GUARD_TIERS.md?collection=docs" +import * as __fd_glob_56 from "../docs/security/PUBLIC_CREDS.md?collection=docs" +import * as __fd_glob_55 from "../docs/security/GUARDRAILS.md?collection=docs" +import * as __fd_glob_54 from "../docs/security/ERROR_SANITIZATION.md?collection=docs" +import * as __fd_glob_53 from "../docs/security/COMPLIANCE.md?collection=docs" +import * as __fd_glob_52 from "../docs/security/CLI_TOKEN_AUTH.md?collection=docs" +import * as __fd_glob_51 from "../docs/security/CLI_TOKEN.md?collection=docs" +import * as __fd_glob_50 from "../docs/routing/REASONING_REPLAY.md?collection=docs" +import * as __fd_glob_49 from "../docs/routing/QUOTA_SHARE.md?collection=docs" +import * as __fd_glob_48 from "../docs/routing/AUTO-COMBO.md?collection=docs" +import * as __fd_glob_47 from "../docs/reference/PROVIDER_REFERENCE.md?collection=docs" +import * as __fd_glob_46 from "../docs/reference/FREE_TIERS.md?collection=docs" +import * as __fd_glob_45 from "../docs/reference/ENVIRONMENT.md?collection=docs" +import * as __fd_glob_44 from "../docs/reference/CLI-TOOLS.md?collection=docs" +import * as __fd_glob_43 from "../docs/reference/API_REFERENCE.md?collection=docs" +import * as __fd_glob_42 from "../docs/ops/VM_DEPLOYMENT_GUIDE.md?collection=docs" +import * as __fd_glob_41 from "../docs/ops/TUNNELS_GUIDE.md?collection=docs" +import * as __fd_glob_40 from "../docs/ops/SQLITE_RUNTIME.md?collection=docs" +import * as __fd_glob_39 from "../docs/ops/RELEASE_CHECKLIST.md?collection=docs" +import * as __fd_glob_38 from "../docs/ops/PROXY_GUIDE.md?collection=docs" +import * as __fd_glob_37 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs" +import * as __fd_glob_36 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs" +import * as __fd_glob_35 from "../docs/ops/COVERAGE_PLAN.md?collection=docs" +import * as __fd_glob_34 from "../docs/guides/USER_GUIDE.md?collection=docs" +import * as __fd_glob_33 from "../docs/guides/UNINSTALL.md?collection=docs" +import * as __fd_glob_32 from "../docs/guides/TROUBLESHOOTING.md?collection=docs" +import * as __fd_glob_31 from "../docs/guides/TERMUX_GUIDE.md?collection=docs" +import * as __fd_glob_30 from "../docs/guides/SETUP_GUIDE.md?collection=docs" +import * as __fd_glob_29 from "../docs/guides/PWA_GUIDE.md?collection=docs" +import * as __fd_glob_28 from "../docs/guides/KIRO_SETUP.md?collection=docs" +import * as __fd_glob_27 from "../docs/guides/I18N.md?collection=docs" +import * as __fd_glob_26 from "../docs/guides/FEATURES.md?collection=docs" +import * as __fd_glob_25 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs" +import * as __fd_glob_24 from "../docs/guides/DOCKER_GUIDE.md?collection=docs" +import * as __fd_glob_23 from "../docs/frameworks/WEBHOOKS.md?collection=docs" +import * as __fd_glob_22 from "../docs/frameworks/TRAFFIC_INSPECTOR.md?collection=docs" +import * as __fd_glob_21 from "../docs/frameworks/SKILLS.md?collection=docs" +import * as __fd_glob_20 from "../docs/frameworks/OPENCODE.md?collection=docs" +import * as __fd_glob_19 from "../docs/frameworks/MEMORY.md?collection=docs" +import * as __fd_glob_18 from "../docs/frameworks/MCP-SERVER.md?collection=docs" +import * as __fd_glob_17 from "../docs/frameworks/GAMIFICATION.md?collection=docs" +import * as __fd_glob_16 from "../docs/frameworks/EVALS.md?collection=docs" +import * as __fd_glob_15 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs" +import * as __fd_glob_14 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs" +import * as __fd_glob_13 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs" +import * as __fd_glob_12 from "../docs/frameworks/AGENTBRIDGE.md?collection=docs" +import * as __fd_glob_11 from "../docs/frameworks/A2A-SERVER.md?collection=docs" +import * as __fd_glob_10 from "../docs/compression/RTK_COMPRESSION.md?collection=docs" +import * as __fd_glob_9 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs" +import * as __fd_glob_8 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs" +import * as __fd_glob_7 from "../docs/compression/COMPRESSION_GUIDE.md?collection=docs" +import * as __fd_glob_6 from "../docs/compression/COMPRESSION_ENGINES.md?collection=docs" +import * as __fd_glob_5 from "../docs/architecture/RESILIENCE_GUIDE.md?collection=docs" +import * as __fd_glob_4 from "../docs/architecture/REPOSITORY_MAP.md?collection=docs" +import * as __fd_glob_3 from "../docs/architecture/MONITORING_SECTIONS.md?collection=docs" import * as __fd_glob_2 from "../docs/architecture/CODEBASE_DOCUMENTATION.md?collection=docs" import * as __fd_glob_1 from "../docs/architecture/AUTHZ_GUIDE.md?collection=docs" import * as __fd_glob_0 from "../docs/architecture/ARCHITECTURE.md?collection=docs" @@ -73,4 +77,4 @@ const create = server({"doc":{"passthroughs":["extractedReferences"]}}); -export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_56, "architecture/meta.json": __fd_glob_57, "compression/meta.json": __fd_glob_58, "frameworks/meta.json": __fd_glob_59, "guides/meta.json": __fd_glob_60, "ops/meta.json": __fd_glob_61, "reference/meta.json": __fd_glob_62, "reference/openapi.yaml": __fd_glob_63, "routing/meta.json": __fd_glob_64, "security/meta.json": __fd_glob_65, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "guides/DOCKER_GUIDE.md": __fd_glob_10, "guides/ELECTRON_GUIDE.md": __fd_glob_11, "guides/FEATURES.md": __fd_glob_12, "guides/I18N.md": __fd_glob_13, "guides/KIRO_SETUP.md": __fd_glob_14, "guides/PWA_GUIDE.md": __fd_glob_15, "guides/SETUP_GUIDE.md": __fd_glob_16, "guides/TERMUX_GUIDE.md": __fd_glob_17, "guides/TROUBLESHOOTING.md": __fd_glob_18, "guides/UNINSTALL.md": __fd_glob_19, "guides/USER_GUIDE.md": __fd_glob_20, "frameworks/A2A-SERVER.md": __fd_glob_21, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_22, "frameworks/CLOUD_AGENT.md": __fd_glob_23, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_24, "frameworks/EVALS.md": __fd_glob_25, "frameworks/GAMIFICATION.md": __fd_glob_26, "frameworks/MCP-SERVER.md": __fd_glob_27, "frameworks/MEMORY.md": __fd_glob_28, "frameworks/OPENCODE.md": __fd_glob_29, "frameworks/SKILLS.md": __fd_glob_30, "frameworks/WEBHOOKS.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/SOCKET_DEV_FINDINGS.md": __fd_glob_54, "security/STEALTH_GUIDE.md": __fd_glob_55, }); \ No newline at end of file +export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_60, "architecture/meta.json": __fd_glob_61, "compression/meta.json": __fd_glob_62, "frameworks/meta.json": __fd_glob_63, "guides/meta.json": __fd_glob_64, "ops/meta.json": __fd_glob_65, "reference/meta.json": __fd_glob_66, "reference/openapi.yaml": __fd_glob_67, "routing/meta.json": __fd_glob_68, "security/meta.json": __fd_glob_69, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/MONITORING_SECTIONS.md": __fd_glob_3, "architecture/REPOSITORY_MAP.md": __fd_glob_4, "architecture/RESILIENCE_GUIDE.md": __fd_glob_5, "compression/COMPRESSION_ENGINES.md": __fd_glob_6, "compression/COMPRESSION_GUIDE.md": __fd_glob_7, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_8, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_9, "compression/RTK_COMPRESSION.md": __fd_glob_10, "frameworks/A2A-SERVER.md": __fd_glob_11, "frameworks/AGENTBRIDGE.md": __fd_glob_12, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_13, "frameworks/CLOUD_AGENT.md": __fd_glob_14, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_15, "frameworks/EVALS.md": __fd_glob_16, "frameworks/GAMIFICATION.md": __fd_glob_17, "frameworks/MCP-SERVER.md": __fd_glob_18, "frameworks/MEMORY.md": __fd_glob_19, "frameworks/OPENCODE.md": __fd_glob_20, "frameworks/SKILLS.md": __fd_glob_21, "frameworks/TRAFFIC_INSPECTOR.md": __fd_glob_22, "frameworks/WEBHOOKS.md": __fd_glob_23, "guides/DOCKER_GUIDE.md": __fd_glob_24, "guides/ELECTRON_GUIDE.md": __fd_glob_25, "guides/FEATURES.md": __fd_glob_26, "guides/I18N.md": __fd_glob_27, "guides/KIRO_SETUP.md": __fd_glob_28, "guides/PWA_GUIDE.md": __fd_glob_29, "guides/SETUP_GUIDE.md": __fd_glob_30, "guides/TERMUX_GUIDE.md": __fd_glob_31, "guides/TROUBLESHOOTING.md": __fd_glob_32, "guides/UNINSTALL.md": __fd_glob_33, "guides/USER_GUIDE.md": __fd_glob_34, "ops/COVERAGE_PLAN.md": __fd_glob_35, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_36, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_37, "ops/PROXY_GUIDE.md": __fd_glob_38, "ops/RELEASE_CHECKLIST.md": __fd_glob_39, "ops/SQLITE_RUNTIME.md": __fd_glob_40, "ops/TUNNELS_GUIDE.md": __fd_glob_41, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_42, "reference/API_REFERENCE.md": __fd_glob_43, "reference/CLI-TOOLS.md": __fd_glob_44, "reference/ENVIRONMENT.md": __fd_glob_45, "reference/FREE_TIERS.md": __fd_glob_46, "reference/PROVIDER_REFERENCE.md": __fd_glob_47, "routing/AUTO-COMBO.md": __fd_glob_48, "routing/QUOTA_SHARE.md": __fd_glob_49, "routing/REASONING_REPLAY.md": __fd_glob_50, "security/CLI_TOKEN.md": __fd_glob_51, "security/CLI_TOKEN_AUTH.md": __fd_glob_52, "security/COMPLIANCE.md": __fd_glob_53, "security/ERROR_SANITIZATION.md": __fd_glob_54, "security/GUARDRAILS.md": __fd_glob_55, "security/PUBLIC_CREDS.md": __fd_glob_56, "security/ROUTE_GUARD_TIERS.md": __fd_glob_57, "security/SOCKET_DEV_FINDINGS.md": __fd_glob_58, "security/STEALTH_GUIDE.md": __fd_glob_59, }); \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a114d775a2..2a2ccff7c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,57 @@ # Changelog -## [Unreleased] +## [Unreleased] — Group A: AgentBridge + Traffic Inspector (planos 11+12) + +### Added + +- **AgentBridge** (`/dashboard/tools/agent-bridge`) — MITM proxy consolidating 9 IDE agents + (Antigravity, Kiro, GitHub Copilot, OpenAI Codex, Cursor IDE, Zed Industries, Claude Code, + Open Code, Trae stub) with server card, per-agent setup wizard, model mapping table, + bypass list, upstream CA cert support, and redirect from legacy `/dashboard/system/mitm-proxy`. + See `docs/frameworks/AGENTBRIDGE.md`. +- **Traffic Inspector** (`/dashboard/tools/traffic-inspector`) — LLM-aware HTTPS debugger with + 4 capture modes (AgentBridge hook, Custom Hosts DNS, HTTP_PROXY :8080, System-wide proxy), + DevTools split UI, 7 detail tabs (Conversation, Headers, Request, Response, Timing, LLM Details, + Stats), resizable panels, session recording (.har/.jsonl export), SSE stream merger, + conversation normalizer (multi-provider), system-prompt fingerprint colorization, and annotations. + See `docs/frameworks/TRAFFIC_INSPECTOR.md`. +- **MITM handler base + 9 agent handlers** (`src/mitm/handlers/`) — `MitmHandlerBase` abstract + class with `hookBufferStart`/`hookBufferUpdate` for Traffic Inspector integration; concrete + handlers for all 9 agents. +- **MITM targets registry** (`src/mitm/targets/`) — declarative `MitmTarget` shape per agent; + emits `DATA_DIR/mitm/targets.json` for dynamic `server.cjs` resolution. +- **Traffic Inspector core** (`src/mitm/inspector/`) — `TrafficBuffer` in-memory ring, + `kindDetector`, `sseMerger` (MIT port from chouzz/llm-interceptor), `conversationNormalizer` + (MIT port), `contextKey` fingerprinting, `httpProxyServer`, `systemProxyConfig`. +- **AgentBridge passthrough + bypass** (`src/mitm/passthrough.ts`) — TCP tunnel for + non-mapped hosts; bypass list with default sensitive-host patterns + user-defined patterns. +- **Upstream CA cert** (`src/mitm/upstreamTrust.ts`) — `AGENTBRIDGE_UPSTREAM_CA_CERT` for + corporate TLS environments. +- **Secret masking** (`src/mitm/maskSecrets.ts`) — sk-/Bearer/generic token masking before + any log or Traffic Inspector broadcast. +- **DB migrations 073–075** — `agent_bridge_state`, `agent_bridge_mappings`, + `agent_bridge_bypass`, `inspector_custom_hosts`, `inspector_sessions`, + `inspector_session_requests`. +- **~28 API routes** under `/api/tools/agent-bridge/` (12 routes) and + `/api/tools/traffic-inspector/` (16+ routes). All LOCAL_ONLY + SPAWN_CAPABLE. +- **i18n** PT-BR + EN for all new keys in `agentBridge.*` and `trafficInspector.*` namespaces; + all other locales fall back to EN automatically. +- **E2E smoke tests** — `tests/e2e/agent-bridge.spec.ts`, + `tests/e2e/traffic-inspector.spec.ts`, `tests/e2e/agent-bridge-traffic-cross.spec.ts` + (skip-gated on CI by `RUN_AGENT_BRIDGE_E2E` / `RUN_TRAFFIC_INSPECTOR_E2E` / `RUN_CROSS_E2E`). +- **Documentation** — `docs/frameworks/AGENTBRIDGE.md` and `docs/frameworks/TRAFFIC_INSPECTOR.md`; + `docs/architecture/REPOSITORY_MAP.md` updated; `docs/reference/openapi.yaml` updated with + ~28 new routes and 20+ new schemas. + +### Changed + +- Sidebar Tools group: added `agent-bridge` and `traffic-inspector` items after `cloud-agents`. +- `/api/tools/agent-bridge/` and `/api/tools/traffic-inspector/` added to `LOCAL_ONLY_API_PREFIXES` + and `SPAWN_CAPABLE_PREFIXES` in `src/server/authz/routeGuard.ts`. +- `.env.example`: documented 9 new env vars (`AGENTBRIDGE_UPSTREAM_CA_CERT`, + `INSPECTOR_BUFFER_SIZE`, `INSPECTOR_HTTP_PROXY_PORT`, `INSPECTOR_HTTP_PROXY_AUTOSTART`, + `INSPECTOR_TLS_INTERCEPT`, `INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES`, `INSPECTOR_MAX_BODY_KB`, + `INSPECTOR_MASK_SECRETS`, `INSPECTOR_LLM_HOSTS_EXTRA`, `INSPECTOR_INTERNAL_INGEST_TOKEN`). --- diff --git a/docs/architecture/REPOSITORY_MAP.md b/docs/architecture/REPOSITORY_MAP.md index 6d6cf82416..ea08057bcb 100644 --- a/docs/architecture/REPOSITORY_MAP.md +++ b/docs/architecture/REPOSITORY_MAP.md @@ -101,7 +101,9 @@ src/ ├── shared/ # Shared between server and client where safe (constants, types, validation, contracts, utils) ├── i18n/ # next-intl config + per-locale message JSON (30+ locales) ├── middleware/ # Next.js middleware (request enrichment, locale detection) -├── mitm/ # MITM proxy helpers (Linux cert install, antigravity stealth) +├── mitm/ # MITM proxy core: cert gen/install, handlers, targets, inspector, masks, passthrough +│ ├── handlers/ # 9 IDE-agent handler classes extending MitmHandlerBase (antigravity, kiro, copilot, codex, cursor, zed, claudeCode, openCode, trae) +│ └── inspector/ # Traffic capture layer: buffer (in-memory ring), sseMerger, conversationNormalizer, kindDetector, contextKey, httpProxyServer, systemProxyConfig ├── models/ # Model adapter glue (legacy shim) ├── scripts/ # In-tree maintenance scripts (e.g., backfillAggregation) ├── sse/ # Legacy SSE handlers/services (chat.ts, chatHelpers.ts, services/auth.ts) @@ -120,9 +122,13 @@ src/ | `app/api/v1/` | Public OpenAI-compat API (~25 sub-routes: chat, completions, embeddings, files, batches, audio, images, videos, music, rerank, moderations, search, ws, agents, accounts, providers, etc.) | | `app/api/v1beta/` | Gemini-style API endpoints | | `app/api/` (non-v1) | Management/admin routes (~60 directories: providers, combos, settings, mcp, a2a, evals, memory, skills, webhooks, compliance, resilience, monitoring, tunnels, cli-tools, etc.) | +| `app/api/tools/agent-bridge/` | AgentBridge REST API — 12 routes (server control, agent state/DNS/mappings, bypass, cert, upstream-CA). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/AGENTBRIDGE.md §7`. | +| `app/api/tools/traffic-inspector/` | Traffic Inspector REST + WS API — 16+ routes (requests, sessions, hosts, capture-modes, export, ws). LOCAL_ONLY + SPAWN_CAPABLE. See `docs/frameworks/TRAFFIC_INSPECTOR.md §8`. | | `app/a2a/` | A2A JSON-RPC 2.0 entry point (`POST /a2a`) | | `app/.well-known/agent.json/` | A2A Agent Card (discovery) | | `app/(dashboard)/dashboard/` | Dashboard UI pages (~35 pages: providers, combos, settings, memory, skills, webhooks, evals, audit, batch, cache, costs, health, system, activity, etc.) | +| `app/(dashboard)/dashboard/tools/agent-bridge/` | AgentBridge dashboard page — server card, 9 agent cards, setup wizard, model mapping, bypass list. i18n PT-BR + EN. See `docs/frameworks/AGENTBRIDGE.md`. | +| `app/(dashboard)/dashboard/tools/traffic-inspector/` | Traffic Inspector dashboard page — DevTools split, 7 detail tabs, 4 capture mode toggles, session recorder, context colorization. i18n PT-BR + EN. See `docs/frameworks/TRAFFIC_INSPECTOR.md`. | | `app/(dashboard)/dashboard/activity/` | Activity feed page (Group B): `page.tsx` (server) + `ActivityFeedClient.tsx` + `components/{ActivityFeed,ActivityItem,DayHeader,EventTypeFilter}.tsx` — see `docs/architecture/MONITORING_SECTIONS.md` | | `app/(dashboard)/dashboard/costs/quota-share/` | Quota Sharing page (Group B): `QuotaSharePageClient.tsx` + `components/{PoolCard,DimensionBar,AllocationTable,BurnRateChart,QuotaConceptCard,CreatePoolModal,EditAllocationsModal}.tsx` + `hooks/{usePools,usePoolUsage,useLocalStoragePoolMigration}.ts` | | `app/(dashboard)/dashboard/costs/quota-share/plans/` | Provider plan config page (Group B): `page.tsx` + `ProviderPlanConfigClient.tsx` — quota dimensions per connection override | diff --git a/docs/frameworks/AGENTBRIDGE.md b/docs/frameworks/AGENTBRIDGE.md new file mode 100644 index 0000000000..6e11f7d051 --- /dev/null +++ b/docs/frameworks/AGENTBRIDGE.md @@ -0,0 +1,406 @@ +--- +title: "AgentBridge" +version: 3.8.6 +lastUpdated: 2026-05-28 +--- + +# AgentBridge + +AgentBridge is OmniRoute's MITM (Man-in-the-Middle) proxy that intercepts HTTPS traffic from IDE AI agents and reroutes it through OmniRoute's unified routing engine. It supports **9 IDE agents** — Antigravity, Kiro, GitHub Copilot, OpenAI Codex, Cursor, Zed, Claude Code, Open Code, and Trae (investigating) — making OmniRoute the broadest-coverage MITM proxy for AI coding assistants on the market. + +**Dashboard location:** `/dashboard/tools/agent-bridge` +**Sidebar group:** Tools (after Cloud Agents) +**See also:** [`TRAFFIC_INSPECTOR.md`](./TRAFFIC_INSPECTOR.md) — monitor all intercepted traffic in real-time. + +--- + +## §1 Overview + +### What is AgentBridge? + +When an IDE agent (e.g., GitHub Copilot, Cursor, Claude Code) makes an API call, it connects directly to the upstream AI provider (OpenAI, Anthropic, etc.). AgentBridge intercepts that connection transparently at the TLS level — without requiring any agent configuration change — and rewrites the request through OmniRoute. + +This means you can: + +- **Reroute any agent to any provider**: Copilot talking to OpenAI? Redirect it to Anthropic Claude, Gemini, or any of OmniRoute's 160+ providers. +- **Apply model mappings**: `gemini-3-flash` → `claude-sonnet-4.7` transparently at the handler level. +- **Observe all agent traffic**: every intercepted request is published to the [Traffic Inspector](./TRAFFIC_INSPECTOR.md). +- **Apply OmniRoute resilience**: combo routing, circuit breakers, fallbacks, and cost tracking work for IDE agent traffic too. + +### Positioning vs. the market + +| Feature | 9router | anti-api | llm-interceptor | **OmniRoute AgentBridge** | +|---------|:-------:|:--------:|:---------------:|:-------------------------:| +| Antigravity | ✓ | ✓ | — | ✓ | +| GitHub Copilot | ✓ | ✓ | — | ✓ | +| Kiro (AWS) | ✓ | ✓ | — | ✓ | +| OpenAI Codex | — | ✓ | — | ✓ | +| Cursor IDE | ✓ | ✓ | — | ✓ | +| Zed Industries | — | ✓ | — | ✓ | +| Claude Code | — | — | ✓ | ✓ | +| Open Code | — | — | ✓ | ✓ | +| Trae | — | — | — | 🔍 Investigating | +| Dashboard UI | ✓ | ✗ | ✗ | ✓ | +| Traffic Inspector | ✗ | ✗ | ✓ | ✓ | +| OmniRoute routing | ✗ | ✗ | ✗ | ✓ | +| Model mapping UI | ✗ | ✗ | ✗ | ✓ | +| Bypass list | ✗ | ✗ | ✓ | ✓ | +| Upstream CA cert | ✗ | ✗ | ✓ | ✓ | + +--- + +## §2 Architecture + +### 2.1 Components overview + +``` +IDE Agent (VS Code / Cursor / etc.) + │ HTTPS (port 443) + ▼ +/etc/hosts — 127.0.0.1 api.githubcopilot.com ← DNS redirect + │ + ▼ +src/mitm/server.cjs (port 443, CJS child process) + │ resolves target by Host header SNI + │ generates per-SNI TLS cert signed by AgentBridge CA + ├── Bypass list match? → TCP passthrough (no decrypt) + ├── Target match? → fetch → OmniRoute router (port 20128) + │ └── handler.intercept() — TypeScript + │ ├── maskSecrets() on request body/headers + │ ├── TrafficBuffer.push() — publishes to Traffic Inspector + │ └── fetchRouter() → /v1/chat/completions + └── No match? → TCP passthrough (no decrypt) +``` + +### 2.2 MITM server (`src/mitm/server.cjs`) + +The core MITM server runs as a Node.js CJS child process (to avoid rewriting the existing CJS codebase). It: + +- Listens on port 443 (requires privilege or `authbind`/`setcap`) +- Receives CONNECT tunnels from the OS (via `/etc/hosts` DNS redirect) +- Generates per-SNI TLS certificates signed by the AgentBridge CA (`DATA_DIR/mitm/ca.crt`) +- Resolves the target agent by Host header via `targets/index.ts` registry +- Dispatches to the TypeScript handler layer via HTTP to `http://127.0.0.1:20128` + +`TARGET_HOSTS` is loaded from `DATA_DIR/mitm/targets.json` (written by `targets/index.ts` at boot), allowing dynamic updates without restarting the CJS server. + +### 2.3 Handler base (`src/mitm/handlers/base.ts`) + +All agent handlers extend `MitmHandlerBase`: + +```ts +export abstract class MitmHandlerBase { + abstract readonly agentId: AgentId; + + abstract intercept( + req: IncomingMessage, + res: ServerResponse, + body: Buffer, + mappedModel: string, + ): Promise; + + // Protected helpers: fetchRouter, pipeSSE, hookBufferStart, hookBufferUpdate +} +``` + +Each handler calls `hookBufferStart()` before proxying and `hookBufferUpdate()` when complete. These push `InterceptedRequest` entries into `globalTrafficBuffer` (see [Traffic Inspector](./TRAFFIC_INSPECTOR.md) §4). + +### 2.4 Targets registry (`src/mitm/targets/`) + +Each agent has a declarative target file: + +```ts +// src/mitm/targets/copilot.ts +export const COPILOT_TARGET: MitmTarget = { + id: "copilot", + name: "GitHub Copilot", + hosts: ["api.githubcopilot.com", "copilot-proxy.githubusercontent.com"], + port: 443, + endpointPatterns: ["/chat/completions", "/v1/chat/completions"], + defaultModels: [ + { id: "gpt-4o", name: "GPT-4o", alias: "gpt-4o" }, + ], + handler: () => import("../handlers/copilot"), + riskNoticeKey: "providers.riskNotice.oauth", +}; +``` + +The registry (`targets/index.ts`) exports `ALL_TARGETS` and emits `DATA_DIR/mitm/targets.json` on boot. + +### 2.5 Passthrough and bypass list (`src/mitm/passthrough.ts`) + +**Bypass list** (checked first, with precedence over target match): +- Default patterns: banking hosts, `.gov.`, OAuth/SSO providers (Okta, Auth0), etc. +- User patterns: stored in DB table `agent_bridge_bypass` +- Bypassed hosts receive a transparent TCP tunnel — TLS is **never decrypted** + +**Passthrough default** (no target match and not in bypass): +- Also receives a TCP tunnel — connections are never broken +- Prevents the AgentBridge from disrupting general system HTTPS traffic + +Routing precedence: +``` +bypass list → target match → passthrough +``` + +### 2.6 Upstream CA cert (`src/mitm/upstreamTrust.ts`) + +For corporate network environments with a custom CA: + +```bash +AGENTBRIDGE_UPSTREAM_CA_CERT=/path/to/corporate-ca.pem +``` + +When set, configures `undici`'s global dispatcher with the extra CA cert, allowing AgentBridge to reach upstream providers through corporate TLS termination proxies. + +### 2.7 Secret masking (`src/mitm/maskSecrets.ts`) + +Applied to all request bodies and headers **before** they enter the Traffic Inspector buffer or any log: + +- `sk-` / `ak-` / `pk-` prefixed tokens (OpenAI/Anthropic-style) +- `Authorization: Bearer ` headers +- Generic long tokens (≥40 chars) + +--- + +## §3 Setup + +### 3.1 Start/stop the MITM server + +Use the AgentBridge Server Card at `/dashboard/tools/agent-bridge`: + +| Action | Description | +|--------|-------------| +| Start Server | Spawns `src/mitm/server.cjs` on port 443 | +| Stop Server | Gracefully shuts down the child process | +| Restart Server | Stop + start (picks up target changes) | +| Trust Cert | Installs `DATA_DIR/mitm/ca.crt` into OS trust store | +| Download Cert | Downloads `ca.crt` for manual installation | +| Regenerate Cert | Creates a new CA keypair (all existing per-agent certs are invalidated) | + +### 3.2 Trust the certificate + +The AgentBridge CA certificate must be trusted by the OS before IDEs will accept the MITM connection. + +**Linux (NSS — Chrome/Firefox):** +```bash +certutil -A -d sql:$HOME/.pki/nssdb -n "OmniRoute AgentBridge" -t CT,, -i ~/.omniroute/mitm/ca.crt +``` + +**macOS (Keychain):** +```bash +sudo security add-trusted-cert -d -r trustRoot \ + -k /Library/Keychains/System.keychain ~/.omniroute/mitm/ca.crt +``` + +**Windows (certmgr):** +```powershell +certutil -addstore -f Root $env:USERPROFILE\.omniroute\mitm\ca.crt +``` + +Or use the "Trust Cert" button in the dashboard (runs the appropriate command for your OS, with sudo prompt if needed). + +### 3.3 DNS routing + +For each agent you want to intercept, its API host(s) must resolve to `127.0.0.1`. AgentBridge manages `/etc/hosts` entries automatically when you toggle DNS for an agent in the Setup Wizard. + +Example `/etc/hosts` entries for GitHub Copilot: +``` +127.0.0.1 api.githubcopilot.com +127.0.0.1 copilot-proxy.githubusercontent.com +``` + +### 3.4 Model mapping + +Use the Model Mapping Table in each agent card to define source → target mappings: + +| Source model (agent native) | Target model (OmniRoute) | +|-----------------------------|--------------------------| +| `gpt-4o` | `claude-sonnet-4.7` | +| `*` (wildcard) | `claude-haiku-4.7` | + +Wildcard `*` maps any unrecognized model to the specified target. Persisted in `agent_bridge_mappings` table. + +### 3.5 Risk notice + +AgentBridge intercepts credentials (OAuth tokens, API keys) that the IDE uses to authenticate with upstream providers. These are **masked before logging** (see §2.7) but are visible to OmniRoute's MITM layer. First activation of each agent shows a dismissible risk notice modal. + +--- + +## §4 Per-agent reference + +| # | Agent | Status | Hosts intercepted | Auth type | +|---|-------|--------|-------------------|-----------| +| 1 | **Antigravity** | ✅ Supported | `daily-cloudcode-pa.googleapis.com`, `cloudcode-pa.googleapis.com` | Firebase OAuth | +| 2 | **Kiro (AWS)** | ✅ Supported | `prod.kiro.aws`, `dev.kiro.aws` | AWS SigV4 | +| 3 | **GitHub Copilot** | ✅ Supported | `api.githubcopilot.com`, `copilot-proxy.githubusercontent.com` | GitHub OAuth | +| 4 | **OpenAI Codex** | ✅ Supported | `api.openai.com` (Codex paths), `chatgpt.com` | OpenAI key | +| 5 | **Cursor IDE** | ✅ Supported | `api2.cursor.sh`, `api.cursor.sh` | Cursor OAuth | +| 6 | **Zed Industries** | ✅ Supported | `api.zed.dev`, `llm.zed.dev` | Zed OAuth | +| 7 | **Claude Code** | ✅ Supported | `api.anthropic.com` (opt-in) | Anthropic key | +| 8 | **Open Code** | ✅ Supported | `openrouter.ai`, `api.openai.com` (zen paths) | API key | +| 9 | **Trae** | 🔍 Investigating | TBD — see §8 | TBD | + +### Setup wizard steps (per agent) + +Each agent card has a 3-step setup wizard: + +1. **Verify prerequisites** — Server running? Cert trusted? IDE installed (auto-detected)? +2. **Enable DNS** — Adds `/etc/hosts` entries (requires sudo). Shows exactly which lines will be added. +3. **Map models** — Optional model mapping table. Wildcards accepted. + +### Agent detection + +For agents 1–8, AgentBridge attempts to auto-detect IDE installation: + +```ts +export async function detectAgent(agentId: AgentId): Promise +// Returns: { installed: boolean, version?: string, path?: string } +``` + +Detection uses OS-specific paths and binary checks (e.g., `code --list-extensions | grep github.copilot` for Copilot, `~/.config/antigravity/` for Antigravity). + +--- + +## §5 Security + +### Hard Rules applied + +| Rule | Application | +|------|-------------| +| **#12** `sanitizeErrorMessage` | All handler errors are sanitized before response or buffer entry | +| **#13** Shell env-passing | `/etc/hosts` edits use `env` option — no string interpolation of paths | +| **#15 + #17** `isLocalOnlyPath()` | `/api/tools/agent-bridge/` is LOCAL_ONLY + SPAWN_CAPABLE — loopback enforced before auth | + +### Bypass list for sensitive hosts + +The bypass list ensures that financial institutions, OAuth/SSO providers, and other sensitive hosts are **never decrypted**. Their TLS traffic passes through as a transparent TCP tunnel — OmniRoute never sees the plaintext. + +Default bypass patterns include: +- `*.bank.*`, `*.gov.*` (financial/government) +- `*.okta.com`, `*.auth0.com`, `*.microsoft.com` (SSO/identity) +- `*.apple.com`, `*.icloud.com` (Apple system services) + +User-added bypass patterns are stored in `agent_bridge_bypass` table and take precedence over everything. + +### Secret masking + +`maskSecrets()` from `src/mitm/maskSecrets.ts` is applied: +- On every request body before `TrafficBuffer.push()` +- On every header before logging or broadcasting + +Patterns: `sk-`/`ak-`/`pk-` prefix tokens, `Bearer` tokens, and generic tokens ≥40 characters. + +### Upstream CA cert + +When `AGENTBRIDGE_UPSTREAM_CA_CERT` is set, the file is read at startup. If the path exists but the file is unreadable, AgentBridge logs a clear error and refuses to start (prevents silent TLS failures in corporate environments). + +### Known limitations + +- **Port 443 requires privilege**: On Linux, AgentBridge needs `setcap 'cap_net_bind_service=+ep'` on the Node binary, or run via `authbind`. The Setup Wizard displays OS-specific instructions. +- **IDE restart required**: After DNS redirect, the IDE must be restarted for the new host resolution to take effect. +- **Hardcoded OAuth tokens**: Some agents (Kiro, Antigravity) store OAuth refresh tokens locally. These are transparent to AgentBridge — it sees the Bearer token in each request, which is masked before logging. + +--- + +## §6 Troubleshooting + +### Port 443 conflict + +If another process is already listening on port 443 (web server, VPN, etc.): + +```bash +lsof -i :443 # find the process +sudo fuser -k 443/tcp # force-kill (use with care) +``` + +Alternatively, configure a non-privileged port in AgentBridge settings and set up `iptables` / `pf` redirect rules. + +### Certificate not trusted + +If the IDE shows TLS errors after starting AgentBridge: + +1. Verify the cert was installed: `security find-certificate -c "OmniRoute AgentBridge"` (macOS) or `certutil -L -d sql:$HOME/.pki/nssdb` (Linux/NSS) +2. Some apps maintain their own trust store (Firefox, Chrome on Linux). Run "Trust Cert" again and check the NSS/Firefox-specific cert store. +3. Restart the IDE after trusting — in-flight TLS sessions use the old trust state. + +### DNS not propagated + +Check that `/etc/hosts` was updated: +```bash +grep "omniroute\|127.0.0.1.*github\|127.0.0.1.*cursor" /etc/hosts +``` + +Flush DNS cache: +```bash +# macOS +sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder +# Linux (systemd-resolved) +sudo systemctl restart systemd-resolved +# Windows +ipconfig /flushdns +``` + +### IDE not detected + +Auto-detection uses common installation paths. If detection fails but the IDE is installed: +- Check if the IDE binary is in a non-standard location +- The Setup Wizard still works — detection failure just means the badge won't show the install path + +### Handler errors (upstream fetch fails) + +If AgentBridge intercepts but all requests fail: +1. Verify at least one provider is connected at `/dashboard/providers` +2. Check OmniRoute server logs: `APP_LOG_LEVEL=debug` in `.env` +3. Verify `OMNIROUTE_BASE_URL` points to the correct router endpoint (default: `http://127.0.0.1:20128`) + +--- + +## §7 API reference + +All routes are `LOCAL_ONLY` (loopback-only, enforced before auth) and `SPAWN_CAPABLE`. See `src/server/authz/routeGuard.ts`. + +Base path: `/api/tools/agent-bridge/` + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/tools/agent-bridge/agents` | List all 9 agents with current state | +| GET | `/api/tools/agent-bridge/state` | Global server state (running, port, cert info) | +| POST | `/api/tools/agent-bridge/server` | Start/stop/restart server (`action: "start"\|"stop"\|"restart"\|"trust-cert"\|"regenerate-cert"`) | +| GET | `/api/tools/agent-bridge/agents/{id}/state` | State of one agent (dns_enabled, cert_trusted, etc.) | +| POST | `/api/tools/agent-bridge/agents/{id}/dns` | Enable/disable DNS for agent (`{enabled: boolean}`) | +| GET | `/api/tools/agent-bridge/agents/{id}/mappings` | Model mappings for agent | +| PUT | `/api/tools/agent-bridge/agents/{id}/mappings` | Update model mappings | +| GET | `/api/tools/agent-bridge/bypass` | List bypass patterns | +| PUT | `/api/tools/agent-bridge/bypass` | Update bypass patterns | +| POST | `/api/tools/agent-bridge/cert` | Download or regenerate CA cert | +| GET | `/api/tools/agent-bridge/upstream-ca` | Get configured upstream CA path | +| POST | `/api/tools/agent-bridge/upstream-ca` | Set upstream CA cert path | + +Full OpenAPI schemas: `docs/reference/openapi.yaml` → tag `AgentBridge`. + +--- + +## §8 Roadmap + +### Trae investigation + +Trae is a relatively new AI coding assistant. Before implementing a handler: + +1. Identify the binary/extension in VS Code / JetBrains marketplaces or as a standalone app +2. Capture traffic with mitmproxy to discover API hosts and endpoint shapes +3. Determine authentication mechanism +4. Assess go/no-go based on TOS and API discoverability + +Until investigation completes, the Trae card in the dashboard shows a "Investigating" badge with a "Report viability" link. The handler stub at `src/mitm/handlers/trae.ts` throws a structured `Not yet implemented` error. + +### Backlog agents (MITM required — no custom base URL support) + +The following tools do not support custom base URLs in their current versions, making MITM the only interception path. Viability assessment is pending: + +- **Windsurf** (Codeium/Cognition) +- **Amp** (Sourcegraph) +- **Amazon Q / Kiro CLI** (AWS Bedrock — separate from Kiro IDE) +- **Cowork** (Anthropic desktop) + +Note: GitHub Copilot CLI ≥v1.0.19 supports `COPILOT_PROVIDER_BASE_URL` — use direct config instead of MITM for that tool. diff --git a/docs/frameworks/TRAFFIC_INSPECTOR.md b/docs/frameworks/TRAFFIC_INSPECTOR.md new file mode 100644 index 0000000000..c48b213930 --- /dev/null +++ b/docs/frameworks/TRAFFIC_INSPECTOR.md @@ -0,0 +1,421 @@ +--- +title: "Traffic Inspector" +version: 3.8.6 +lastUpdated: 2026-05-28 +--- + +# Traffic Inspector + +Traffic Inspector is OmniRoute's built-in HTTPS traffic debugger — a Charles Proxy / mitmweb / HTTP Toolkit-like tool that is **LLM-aware** and **agent-aware**. It lives at `/dashboard/tools/traffic-inspector` and receives live traffic from up to 4 simultaneous capture sources. + +**Dashboard location:** `/dashboard/tools/traffic-inspector` +**Sidebar group:** Tools (after AgentBridge) +**See also:** [`AGENTBRIDGE.md`](./AGENTBRIDGE.md) — AgentBridge is capture mode 1. + +--- + +## §1 Overview + +### What makes Traffic Inspector unique + +| Feature | mitmweb | Charles | Fiddler | **OmniRoute Traffic Inspector** | +|---------|:-------:|:-------:|:-------:|:-------------------------------:| +| Web-based | ✓ | ✗ | ✗ | ✓ | +| Open-source | ✓ | ✗ | partial | ✓ | +| **Agent-aware** (knows if request is from Antigravity/Copilot/etc.) | ✗ | ✗ | ✗ | ✓ | +| **LLM-aware** (parses OpenAI/Anthropic/Gemini shape, tokens, model) | ✗ | ✗ | ✗ | ✓ | +| **Model mapping visible** (gemini-3-flash → claude-sonnet-4.7) | ✗ | ✗ | ✗ | ✓ | +| **Proxy/upstream latency split** | partial | ✗ | ✗ | ✓ | +| **Integrated with OmniRoute** routing, fallback, cost | ✗ | ✗ | ✗ | ✓ | +| **System-wide proxy debug** (any app on the machine) | ✓ | ✓ | ✓ | ✓ | +| **Custom host capture** (per-host DNS redirect) | ✓ | ✓ | ✓ | ✓ | +| **HTTP_PROXY env mode** | ✓ | ✓ | ✓ | ✓ | +| **Conversation view** (multi-turn bubbles, tool_use/tool_result) | ✗ | ✗ | ✗ | ✓ | +| **SSE stream merger** (reconstruct from delta events) | ✗ | ✗ | ✗ | ✓ | +| **Session recording** (named, exportable .har/.jsonl) | ✗ | ✓ | ✓ | ✓ | + +### Architecture in one paragraph + +The `TrafficBuffer` (`src/mitm/inspector/buffer.ts`) is a shared in-memory ring buffer (default 1000 entries, configurable via `INSPECTOR_BUFFER_SIZE`). All capture sources write to it via `push()`. The buffer classifies each entry using `kindDetector.ts` (determines if it's an LLM request), computes a `contextKey` (SHA-256 fingerprint of the system prompt), and broadcasts to all WebSocket subscribers via `globalTrafficBuffer.subscribe()`. The dashboard connects via `GET /api/tools/traffic-inspector/ws` and receives a snapshot on connect, followed by `new`/`update`/`clear` events. + +--- + +## §2 Capture modes + +Traffic Inspector supports **4 simultaneous capture sources**. Each is independently toggleable. + +### Mode 1 — AgentBridge (default, always on) + +**Source:** AgentBridge handlers (`src/mitm/handlers/base.ts`) +**Mechanism:** Every `intercept()` call in `MitmHandlerBase` calls `hookBufferStart()` before forwarding and `hookBufferUpdate()` on completion. Zero extra config — works as soon as AgentBridge is running. +**Reach:** The 9 IDE agents configured in AgentBridge +**Note:** `source` field in `InterceptedRequest` = `"agent-bridge"` + +### Mode 2 — Custom Hosts (DNS redirect) + +**Source:** User-defined host list (`inspector_custom_hosts` table) +**Mechanism:** Adding a host via the UI adds `127.0.0.1 ` to `/etc/hosts` (requires sudo). The existing AgentBridge MITM server (port 443) generates a SNI cert dynamically for the new host. +**Reach:** Any application using the added host — no app config change needed +**Note:** `source` = `"custom-host"` + +Example use cases: +- Monitor `api.openai.com` from Python scripts +- Debug `my-internal-llm.company.com` +- Capture traffic from mobile devices on the same network (via ARP spoofing — advanced) + +### Mode 3 — HTTP_PROXY listener (port 8080) + +**Source:** Applications using `HTTP_PROXY`/`HTTPS_PROXY` environment variables +**Mechanism:** Secondary listener at port 8080 (`src/mitm/inspector/httpProxyServer.ts`) that acts as a standard explicit HTTP/HTTPS proxy. Accepts `CONNECT` tunnels (HTTPS) and direct HTTP requests. +**Reach:** Any application that respects `HTTP_PROXY` env — no DNS change, no sudo +**Note:** `source` = `"http-proxy"` + +```bash +# Quick capture for a single command: +HTTPS_PROXY=http://127.0.0.1:8080 curl https://api.openai.com/v1/models + +# Persistent capture in a shell session: +export HTTP_PROXY=http://127.0.0.1:8080 +export HTTPS_PROXY=http://127.0.0.1:8080 +``` + +**TLS limitation:** HTTPS `CONNECT` tunnels are captured as metadata only (host, port, timing) — TLS body is not decrypted by default. Enable "Decrypt HTTPS in proxy mode" toggle (opt-in, requires AgentBridge cert to be trusted) for full body inspection. + +**Port conflict:** If port 8080 is in use, AgentBridge returns a 409 with a structured error. Change the port via `INSPECTOR_HTTP_PROXY_PORT` env var. + +### Mode 4 — System-wide proxy (advanced, opt-in) + +**Source:** OS-level proxy settings (applies to all apps on the machine) +**Mechanism:** Uses OS APIs to redirect all HTTP/HTTPS traffic through the HTTP_PROXY listener: +- **macOS:** `networksetup -setwebproxy / -setsecurewebproxy` +- **Linux:** `gsettings set org.gnome.system.proxy` + `/etc/environment` +- **Windows:** `netsh winhttp set proxy 127.0.0.1:8080` +**Reach:** Every application on the machine that respects system proxy settings +**Note:** `source` = `"system-proxy"` + +**Safety mechanisms:** +- Auto-disable timer (default 30 min, configurable via `INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES`) +- Previous system proxy state is saved in DB and restored on revert +- Dashboard shows "Reverting system proxy" prompt if user navigates away while active +- UI shows `⚠ Advanced` badge + explicit confirmation checkbox + +### Capture mode comparison + +| Mode | Setup | Sudo? | Reach | Notes | +|------|-------|:-----:|-------|-------| +| 1. AgentBridge | Automatic | Once (cert+hosts) | 9 IDE agents | Default on | +| 2. Custom Hosts | Per-host input | Yes (hosts file) | Any app using that host | Persisted in DB | +| 3. HTTP_PROXY | `export HTTPS_PROXY=...` | No | Apps respecting env | Port 8080, no TLS decrypt by default | +| 4. System-wide | Toggle + confirm | Yes | All apps on machine | Auto-disable in 30 min | + +--- + +## §3 UI + +### 3.1 Layout + +``` +┌─ Traffic Inspector ─────────────────────────────────────────────────────┐ +│ ┌─ Capture sources toolbar ─────────────────────────────────────────┐ │ +│ │ [✓ AgentBridge] [✓ Custom hosts (3)] [○ HTTP_PROXY] [○ System]│ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +│ ┌─ Filter/control bar ──────────────────────────────────────────────┐ │ +│ │ Profile: (●) LLM only (○) Custom (○) All │ │ +│ │ [⎉ Pause] [🗑 Clear] [⬇ .har] [● REC session] ● live 482/1k │ │ +│ └─────────────────────────────────────────────────────────────────────┘ │ +├══◀▶══════════════════════════════╬══════════════════════════════════════╤╡ +│ REQUEST LIST (resizable) ║ DETAIL PANE ▲ │ +│ ────────────────────────────── │ ║ [Conversation][Headers][Request] │ │ +│ ▎ 14:32 POST 200 12k AG openai ║ [Response][Timing][LLM][Stats] │ │ +│ ▎ 14:31 POST 200 8k CP openai ║ ▼ │ +│ ▎ 14:31 POST 503 ⚠ KR ... ║ │ +│ ▎ 14:30 GET 200 3k 🌐 custom ║ │ +└══════════════════════════════════╝══════════════════════════════════════╝ +``` + +### 3.2 Request list (left panel) + +- **Virtualized** (`useVirtualList` + `ResizeObserver`): handles 1000 items without freezing +- **Auto-scroll** with toggle to pause while inspecting +- **Color-coded status**: green (2xx), yellow (3xx), red (4xx/5xx), gray (in-flight) +- **Agent emoji**: 🔵 Antigravity, 🟢 Copilot, 🟠 Kiro, 🟣 Codex, 🔷 Cursor, 🟤 Zed, 🟡 Claude Code, ⚫ Open Code, 🌐 custom host +- **Context color bar**: 1px left border colored by `contextKey` (SHA-256 of system prompt) — visually groups related conversations +- **Lazy body**: only the selected request's body is materialized in the detail tabs (avoids rendering 1000 × 1MB bodies) + +### 3.3 Detail pane — 7 tabs + +| Tab | Content | Notes | +|-----|---------|-------| +| **Conversation** | Multi-turn chat bubbles (system/user/assistant + tool_use/tool_result) | Normalized from any provider format; only shown for `detectedKind === "llm"` | +| **Headers** | Request + response header tables | Sensitive headers (Authorization, Cookie, api-key) masked by default; "Show secrets" toggle | +| **Request** | Raw body, JSON tree view, model field badge | Pretty-printed JSON or raw text | +| **Response** | Raw body or SSE event list; toggle "Raw ↔ Merged" | SSE merger reconstructs final message from delta events | +| **Timing** | Waterfall: proxy overhead vs upstream latency | Total, TTFB, and size | +| **LLM Details** | Provider, model, messages count, tokens in/out, cost estimate, mapped target | Only shown for LLM requests | +| **Stats** | Recharts: latency timeline, token bar chart, tool call scatter | Only shown when a recorded session is loaded | + +### 3.4 Toolbar controls + +| Control | Action | +|---------|--------| +| ⎉ Pause | Stops rendering new requests; "X new" badge accumulates | +| 🗑 Clear | Clears the UI list (server buffer is not affected) | +| ⬇ Export .har | Downloads current filtered list as HAR file | +| ● Record session | Starts a named recording session | +| Profile selector | LLM only / Custom hosts / All | +| Host filter | Substring match on `host` field | +| Agent filter | Dropdown: All / per-agent | +| Status filter | All / 2xx / 3xx / 4xx / 5xx / error | +| Source filter | All / agent-bridge / custom-host / http-proxy / system-proxy | + +### 3.5 Resizable panels + +- List and detail pane separated by a drag handle +- List width: min 280px, max 720px, persisted in `localStorage` (`inspector.listWidth`) +- Collapsible to a 48px rail (icon-only); click a row in the rail to expand + +--- + +## §4 LLM-aware features + +### 4.1 Kind detector (`src/mitm/inspector/kindDetector.ts`) + +Classifies each request as `"llm"`, `"app"`, or `"unknown"` using 4 signals: + +1. **Host registry** — ~18 known LLM API hostnames (OpenAI, Anthropic, Gemini, Groq, Mistral, Together, Fireworks, Cohere, Perplexity, Hugging Face, OpenRouter, xAI, Moonshot, etc.) +2. **Path patterns** — `/v1/chat/completions`, `/v1/messages`, `/generateContent`, `/v1/responses`, etc. +3. **Body shape** — detects `messages[]` (OpenAI/Claude), `contents[]` (Gemini), `prompt`, `input` fields +4. **User-agent hints** — `codex`, `claude`, `gemini`, `antigravity`, `kiro`, `copilot`, `cursor` in UA string + +Custom hosts added via Mode 2 inherit their `kind` from the form input (defaults to `"custom"`). + +### 4.2 SSE merger (`src/mitm/inspector/sseMerger.ts`) + +**MIT port from [chouzz/llm-interceptor](https://github.com/chouzz/llm-interceptor)** + +Reconstructs the final assistant message from raw SSE delta events: + +- **Anthropic**: accumulates `content_block_delta` by index; handles `text_delta`, `input_json_delta` (tool calls), `thinking_delta` +- **OpenAI**: accumulates `choices[i].delta.content` and `tool_calls` by index +- **Gemini**: accumulates `candidates[i].content.parts` +- **Unknown**: returns raw events as-is + +The Response tab shows a toggle: **"Raw events ↔ Merged"**. + +### 4.3 Conversation normalizer (`src/mitm/inspector/conversationNormalizer.ts`) + +**MIT port from [chouzz/llm-interceptor](https://github.com/chouzz/llm-interceptor)** + +Converts OpenAI, Anthropic, and Gemini message formats to a single `NormalizedConversation` before rendering: + +```ts +interface NormalizedConversation { + request: NormalizedTurn[]; // messages / contents / prompt from request body + response: NormalizedTurn[]; // assistant response (merged via sseMerger) + contextKey: string | null; // SHA-256 system-prompt fingerprint +} +``` + +Block types: `text`, `tool_use`, `tool_result`. The Conversation tab uses this shape regardless of provider. + +### 4.4 Context key colorization (`src/mitm/inspector/contextKey.ts`) + +- Computes `SHA-256` of the system prompt (first `role:system` message, or `system` field, or Gemini `systemInstruction`) +- Returns a 12-character hex prefix (`"a3f9c2..."`) +- Frontend maps the key to a deterministic HSL color for the left-border bar +- **Filtro "same context"**: clicking the `ctx #a3f` chip adds a filter to show only requests with the same fingerprint + +This makes it easy to visually distinguish different "personas" or tasks running in the same agent session. + +### 4.5 LLM metadata extraction + +For LLM requests, the LLM Details tab extracts: + +```ts +interface LlmMetadata { + provider: string | null; // "openai" | "anthropic" | "gemini" | ... + apiKind: string | null; // "chat.completions" | "messages" | "embeddings" | ... + model: string | null; // from request body or response + messages: number; // turn count + tokensIn: number | null; // usage.prompt_tokens / usage.input_tokens + tokensOut: number | null; // usage.completion_tokens / usage.output_tokens + streamed: boolean; // true if SSE response + mappedTo: string | null; // x-omniroute-mapped header + costEstimateUsd: number | null; // estimated cost based on OmniRoute pricing +} +``` + +--- + +## §5 Sessions + +### 5.1 Recording a session + +1. Click **"● Record session"** in the toolbar → enter a name (optional) +2. Live tail continues normally; a red pulsing indicator shows `◉ REC · · 00:42 · 23 reqs` +3. Click **"⏹ Stop"** → the session snapshot is saved to `inspector_sessions` + `inspector_session_requests` + +### 5.2 Viewing a recorded session + +The **Sessions** dropdown in the toolbar lists saved sessions. Selecting one: +- Loads the session's snapshot (frozen state) +- A banner shows: `Viewing recorded session "" — [Back to live]` +- The Stats tab becomes available with Recharts aggregates + +### 5.3 Export formats + +Each session can be exported as: + +| Format | Use | +|--------|-----| +| **HAR** (HTTP Archive 1.2) | Compatible with Chrome DevTools, Charles, Fiddler — import for offline analysis | +| **JSONL** | One `InterceptedRequest` per line — compatible with `llm-interceptor` format | + +Export via `GET /api/tools/traffic-inspector/sessions/{id}/export.har` or the ⬇ button in the Sessions dropdown. + +--- + +## §6 Security + +Traffic Inspector shows **all intercepted HTTPS traffic**, including authorization headers and request bodies. The following controls are in place: + +| Control | Details | +|---------|---------| +| **LOCAL_ONLY** | All routes and the WebSocket endpoint are loopback-only (enforced in `routeGuard.ts` before auth) | +| **Secret masking** | `maskSecrets()` applied to all headers and bodies before `TrafficBuffer.push()` — enabled by default (`INSPECTOR_MASK_SECRETS=true`) | +| **Body size cap** | Bodies > `INSPECTOR_MAX_BODY_KB` (default 1024 KB) are truncated with `"(truncated for performance)"` notice | +| **Sensitive header masking** | `authorization`, `cookie`, `api-key`, `x-api-key`, `proxy-authorization` → `Bearer ***` in Headers tab; "Show secrets" toggle | +| **CSP** | Strict Content Security Policy on Traffic Inspector pages to prevent XSS via injected response bodies | +| **No persistence by default** | The `TrafficBuffer` is in-memory and lost on server restart. Sessions are persisted only when explicitly recorded | + +### Hard Rules applied + +| Rule | Application | +|------|-------------| +| **#12** `sanitizeErrorMessage` | All HTTP error responses from Traffic Inspector routes are sanitized | +| **#15 + #17** `isLocalOnlyPath()` | `/api/tools/traffic-inspector/` is LOCAL_ONLY + SPAWN_CAPABLE (system proxy commands) | + +### Known limitations + +- **System-wide proxy mode** affects all applications on the machine, including VPN clients and SSO. Always use with the auto-disable timer. Do not use on shared machines. +- **CONNECT tunnel HTTPS**: Mode 3 (HTTP_PROXY) captures only tunnel metadata for HTTPS destinations unless TLS interception is enabled. This is by design — transparent capture without the AgentBridge cert being trusted would break TLS verification for those apps. +- **Hardcoded strings in some components**: Some UI components (F7/F8) have a small number of hardcoded strings not yet covered by i18n keys. These are documented as a Known Limitation in the i18n gap report; they will be migrated in a follow-up pass. Affected strings are UI decorative labels that don't require translation for functional use. + +--- + +## §7 Troubleshooting + +### WebSocket disconnection + +If the live tail shows "Disconnected": +1. Check the server is still running: `GET /api/tools/traffic-inspector/capture-modes` +2. Reload the page — the WebSocket reconnects and receives a fresh snapshot +3. If the server was restarted, the in-memory buffer was cleared — old entries are gone unless a session was recorded + +### Port 8080 conflict + +If HTTP_PROXY mode fails to start: +```bash +lsof -i :8080 # find the process +``` + +Change the port: +```bash +# .env +INSPECTOR_HTTP_PROXY_PORT=8888 +``` + +### System proxy not reverted + +If OmniRoute crashes while system-wide proxy mode is active: + +**macOS:** +```bash +networksetup -setwebproxystate Wi-Fi off +networksetup -setsecurewebproxystate Wi-Fi off +``` + +**Linux (GNOME):** +```bash +gsettings set org.gnome.system.proxy mode 'none' +``` + +**Windows:** +```cmd +netsh winhttp reset proxy +``` + +The dashboard will also offer "Revert system proxy" on next load if it detects the DB state indicates proxy was active. + +### Buffer full + +When the buffer reaches `INSPECTOR_BUFFER_SIZE` (default 1000), new entries rotate out the oldest. If important requests are being lost: +- Increase `INSPECTOR_BUFFER_SIZE` (e.g., 5000) — trades memory for retention +- Record a session to persist the relevant window to DB + +--- + +## §8 API reference + +All routes are `LOCAL_ONLY` (loopback-only) and `SPAWN_CAPABLE` (system proxy commands). See `src/server/authz/routeGuard.ts`. + +Base path: `/api/tools/traffic-inspector/` + +### Request management + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/requests` | List requests (filterable: `?profile=llm&host=&agent=&status=&source=&sessionId=`) | +| GET | `/requests/{id}` | Single request details | +| DELETE | `/requests` | Clear the in-memory buffer | +| POST | `/requests/{id}/replay` | Re-execute the same request through OmniRoute router | +| PUT | `/requests/{id}/annotation` | Save or update a note on a request | + +### WebSocket + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/ws` | Live WebSocket stream. Sends `snapshot` on connect, then `new`/`update`/`clear` events | + +### Export + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/export.har` | Export current filtered list as HAR 1.2 | + +### Custom hosts + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/hosts` | List custom hosts | +| POST | `/hosts` | Add host (auto-edits `/etc/hosts`) | +| DELETE | `/hosts/{host}` | Remove host | +| PATCH | `/hosts/{host}` | Toggle `enabled` | + +### Capture modes + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/capture-modes` | State of all 4 capture modes | +| POST | `/capture-modes/http-proxy` | Start/stop HTTP_PROXY listener (`{action: "start"\|"stop"}`) | +| POST | `/capture-modes/system-proxy` | Apply/revert system-wide proxy (`{action: "apply"\|"revert"}`) | +| POST | `/capture-modes/tls-intercept` | Toggle HTTPS body decryption in proxy mode | + +### Sessions + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/sessions` | Start recording (`{name?: string}`) | +| PATCH | `/sessions/{id}` | Stop or rename (`{action: "stop"\|"rename", name?: string}`) | +| GET | `/sessions` | List all saved sessions | +| GET | `/sessions/{id}` | Session snapshot (all requests) | +| DELETE | `/sessions/{id}` | Delete session | +| GET | `/sessions/{id}/export.har` | Export session as HAR 1.2 | + +### Internal ingest (D4 fallback) + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/internal/ingest` | Accepts intercepted request from `server.cjs` passthrough path; requires `INSPECTOR_INTERNAL_INGEST_TOKEN` header | + +Full OpenAPI schemas: `docs/reference/openapi.yaml` → tag `Traffic Inspector`. diff --git a/docs/reference/ENVIRONMENT.md b/docs/reference/ENVIRONMENT.md index 98ced287c5..c11779d0d7 100644 --- a/docs/reference/ENVIRONMENT.md +++ b/docs/reference/ENVIRONMENT.md @@ -862,6 +862,16 @@ Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy), | `QUOTA_SATURATION_THRESHOLD` | `0.5` | `src/lib/quota/enforce.ts` | Pool saturation ratio (0..1); at/above it the pool enters strict mode (no borrowing). | | `QUOTA_SOFT_DEPRIORITIZE_FACTOR` | `0.7` | `open-sse/services/combo.ts` | Score multiplier (0..1) applied to a target when the soft quota policy deprioritizes it. | | `QUOTA_CONSUMPTION_RETENTION_DAYS` | `14` | `src/lib/db/quotaConsumption.ts` | Retention window (days) for `quota_consumption` buckets before GC (`gcQuotaConsumption`). | +| `AGENTBRIDGE_UPSTREAM_CA_CERT` | _(unset)_ | `src/mitm/manager.ts` | Extra CA certificate (PEM) trusted for AgentBridge upstream TLS connections. | +| `INSPECTOR_BUFFER_SIZE` | `1000` | `src/mitm/inspector/buffer.ts` | Max captured requests held in the Traffic Inspector ring buffer. | +| `INSPECTOR_MAX_BODY_KB` | `1024` | `src/mitm/inspector/buffer.ts` | Max captured request/response body size (KB) before truncation. | +| `INSPECTOR_HTTP_PROXY_PORT` | `8080` | `src/mitm/inspector/httpProxyServer.ts` | Local port for the Traffic Inspector HTTP proxy. | +| `INSPECTOR_HTTP_PROXY_AUTOSTART` | `false` | `src/mitm/inspector/httpProxyServer.ts` | Auto-start the inspector HTTP proxy on boot. | +| `INSPECTOR_TLS_INTERCEPT` | `false` | `src/lib/inspector/captureState.ts` | Enable TLS interception (MITM) for captured HTTPS traffic. | +| `INSPECTOR_LLM_HOSTS_EXTRA` | _(unset)_ | `src/lib/inspector/captureState.ts` | Extra hostnames (comma-separated) treated as LLM endpoints for capture. | +| `INSPECTOR_MASK_SECRETS` | `true` | `src/mitm/inspector/buffer.ts` | Mask secrets (auth headers / API keys) in captured traffic. | +| `INSPECTOR_SYSTEM_PROXY_GUARD_MINUTES` | `30` | `src/app/api/tools/traffic-inspector/capture-modes/system-proxy/route.ts` | Minutes before the system-proxy guard auto-reverts OS proxy settings. | +| `INSPECTOR_INTERNAL_INGEST_TOKEN` | _(auto)_ | `src/app/api/tools/traffic-inspector/internal/ingest/route.ts` | Token authenticating internal capture ingest into the inspector. | --- diff --git a/docs/reference/openapi.yaml b/docs/reference/openapi.yaml index 75dc6cdfcb..58fd93c73f 100644 --- a/docs/reference/openapi.yaml +++ b/docs/reference/openapi.yaml @@ -75,6 +75,19 @@ tags: description: Fallback chain management - name: Telemetry description: Telemetry and token health monitoring + - name: AgentBridge + description: >- + MITM proxy manager for 9 IDE agents (Antigravity, Kiro, Copilot, Codex, Cursor, Zed, + Claude Code, Open Code, Trae). Controls server lifecycle, DNS/model mappings, bypass list, + and cert management. All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17). + See docs/frameworks/AGENTBRIDGE.md. + - name: Traffic Inspector + description: >- + LLM-aware HTTPS traffic debugger with 4 capture modes (AgentBridge, Custom Hosts, + HTTP_PROXY :8080, System-wide). Provides real-time WebSocket stream, session recording, + HAR export, SSE merge, and conversation normalization. + All routes are LOCAL_ONLY + SPAWN_CAPABLE (hard rules #15, #17). + See docs/frameworks/TRAFFIC_INSPECTOR.md. paths: # ─── Proxy Endpoints ────────────────────────────────────────── @@ -3032,6 +3045,609 @@ paths: "200": description: Generated content + # ─── AgentBridge ────────────────────────────────────────────── + + /api/tools/agent-bridge/agents: + get: + tags: [AgentBridge] + summary: List all 9 IDE agents with current state + description: >- + Returns the state (dns_enabled, cert_trusted, setup_completed, last_started_at, + last_error) for all 9 configured IDE agents. LOCAL_ONLY. + responses: + "200": + description: Array of agent state rows + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AgentBridgeAgentState" + "403": + description: Loopback-only — request came from a non-loopback address + + /api/tools/agent-bridge/state: + get: + tags: [AgentBridge] + summary: Get global AgentBridge server state + description: Returns running status, port, cert info, and intercepted request count. + responses: + "200": + description: Server state + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeServerState" + + /api/tools/agent-bridge/server: + post: + tags: [AgentBridge] + summary: Control AgentBridge MITM server + description: Start, stop, restart, trust-cert, or regenerate-cert. SPAWN_CAPABLE. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeServerAction" + responses: + "200": + description: Action executed + "400": + description: Invalid action + "409": + description: Port 443 conflict + + /api/tools/agent-bridge/agents/{agentId}/state: + get: + tags: [AgentBridge] + summary: Get state of one agent + parameters: + - name: agentId + in: path + required: true + schema: + $ref: "#/components/schemas/AgentId" + responses: + "200": + description: Agent state + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeAgentState" + "404": + description: Unknown agent ID + + /api/tools/agent-bridge/agents/{agentId}/dns: + post: + tags: [AgentBridge] + summary: Enable or disable DNS for one agent + description: Adds or removes /etc/hosts entries for the agent's host list. SPAWN_CAPABLE. + parameters: + - name: agentId + in: path + required: true + schema: + $ref: "#/components/schemas/AgentId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeDnsAction" + responses: + "200": + description: DNS updated + "400": + description: Validation error + + /api/tools/agent-bridge/agents/{agentId}/mappings: + get: + tags: [AgentBridge] + summary: Get model mappings for one agent + parameters: + - name: agentId + in: path + required: true + schema: + $ref: "#/components/schemas/AgentId" + responses: + "200": + description: Array of source→target model mappings + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AgentBridgeMappingRow" + put: + tags: [AgentBridge] + summary: Update model mappings for one agent + parameters: + - name: agentId + in: path + required: true + schema: + $ref: "#/components/schemas/AgentId" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeMappingPut" + responses: + "200": + description: Mappings updated + + /api/tools/agent-bridge/bypass: + get: + tags: [AgentBridge] + summary: List bypass patterns (hosts never decrypted) + responses: + "200": + description: Bypass patterns + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/AgentBridgeBypassRow" + put: + tags: [AgentBridge] + summary: Update user bypass patterns + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeBypassUpsert" + responses: + "200": + description: Patterns updated + + /api/tools/agent-bridge/cert: + post: + tags: [AgentBridge] + summary: Download or regenerate the AgentBridge CA certificate + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [action] + properties: + action: + type: string + enum: [download, regenerate] + responses: + "200": + description: CA certificate PEM (download) or regeneration confirmation + + /api/tools/agent-bridge/upstream-ca: + get: + tags: [AgentBridge] + summary: Get configured upstream CA cert path + responses: + "200": + description: Upstream CA configuration + content: + application/json: + schema: + type: object + properties: + path: + type: string + nullable: true + post: + tags: [AgentBridge] + summary: Set upstream CA cert path for corporate TLS environments + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AgentBridgeUpstreamCaPost" + responses: + "200": + description: Upstream CA configured + "400": + description: Path does not exist or is not readable + + # ─── Traffic Inspector ───────────────────────────────────────── + + /api/tools/traffic-inspector/requests: + get: + tags: [Traffic Inspector] + summary: List intercepted requests (filterable) + parameters: + - name: profile + in: query + schema: + type: string + enum: [llm, custom, all] + - name: host + in: query + schema: + type: string + - name: agent + in: query + schema: + $ref: "#/components/schemas/AgentId" + - name: status + in: query + schema: + type: string + enum: ["2xx", "3xx", "4xx", "5xx", error] + - name: source + in: query + schema: + $ref: "#/components/schemas/CaptureSource" + - name: sessionId + in: query + schema: + type: string + format: uuid + responses: + "200": + description: Array of intercepted requests + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InterceptedRequest" + delete: + tags: [Traffic Inspector] + summary: Clear the in-memory traffic buffer + responses: + "204": + description: Buffer cleared + + /api/tools/traffic-inspector/requests/{id}: + get: + tags: [Traffic Inspector] + summary: Get a single intercepted request by ID + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Intercepted request details + content: + application/json: + schema: + $ref: "#/components/schemas/InterceptedRequest" + "404": + description: Request not found in buffer + + /api/tools/traffic-inspector/requests/{id}/replay: + post: + tags: [Traffic Inspector] + summary: Replay a captured request through OmniRoute router + description: Re-executes the original request body against /v1/chat/completions. Consumes quota. + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Replay response (streaming or JSON) + "404": + description: Request not found + + /api/tools/traffic-inspector/requests/{id}/annotation: + put: + tags: [Traffic Inspector] + summary: Save or update annotation on a request + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorAnnotationPut" + responses: + "200": + description: Annotation saved + + /api/tools/traffic-inspector/ws: + get: + tags: [Traffic Inspector] + summary: Live WebSocket stream of intercepted requests + description: >- + Upgrade to WebSocket. On connect, server sends `{type:"snapshot", data:[...]}`. + Subsequent events: `{type:"new", data:{...}}`, `{type:"update", data:{...}}`, + `{type:"clear"}`. LOCAL_ONLY. + responses: + "101": + description: WebSocket upgrade successful + "403": + description: Non-loopback origin rejected + + /api/tools/traffic-inspector/export.har: + get: + tags: [Traffic Inspector] + summary: Export current filtered request list as HAR 1.2 + parameters: + - name: profile + in: query + schema: + type: string + enum: [llm, custom, all] + - name: sessionId + in: query + schema: + type: string + format: uuid + responses: + "200": + description: HAR file (JSON) + content: + application/json: + schema: + type: object + description: HAR 1.2 format + + /api/tools/traffic-inspector/hosts: + get: + tags: [Traffic Inspector] + summary: List custom capture hosts + responses: + "200": + description: Custom hosts list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InspectorCustomHost" + post: + tags: [Traffic Inspector] + summary: Add a custom capture host (edits /etc/hosts) + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorCustomHostCreate" + responses: + "201": + description: Host added + "409": + description: Host already exists + + /api/tools/traffic-inspector/hosts/{host}: + delete: + tags: [Traffic Inspector] + summary: Remove a custom capture host + parameters: + - name: host + in: path + required: true + schema: + type: string + responses: + "204": + description: Host removed + patch: + tags: [Traffic Inspector] + summary: Toggle enabled state of a custom host + parameters: + - name: host + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [enabled] + properties: + enabled: + type: boolean + responses: + "200": + description: Host updated + + /api/tools/traffic-inspector/capture-modes: + get: + tags: [Traffic Inspector] + summary: Get state of all 4 capture modes + responses: + "200": + description: Capture modes state + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorCaptureModesState" + + /api/tools/traffic-inspector/capture-modes/http-proxy: + post: + tags: [Traffic Inspector] + summary: Start or stop the HTTP_PROXY listener (port 8080) + description: SPAWN_CAPABLE — spawns a net.Server listener. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorCaptureModeAction" + responses: + "200": + description: Action executed + "409": + description: Port conflict (EADDRINUSE) when starting + + /api/tools/traffic-inspector/capture-modes/system-proxy: + post: + tags: [Traffic Inspector] + summary: Apply or revert system-wide proxy settings + description: SPAWN_CAPABLE — executes networksetup/gsettings/netsh. Requires admin. + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorSystemProxyAction" + responses: + "200": + description: System proxy updated + "500": + description: OS command failed (permission error) + + /api/tools/traffic-inspector/capture-modes/tls-intercept: + post: + tags: [Traffic Inspector] + summary: Toggle TLS body decryption in HTTP_PROXY mode + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorTlsInterceptToggle" + responses: + "200": + description: TLS intercept mode updated + + /api/tools/traffic-inspector/sessions: + get: + tags: [Traffic Inspector] + summary: List all saved recording sessions + responses: + "200": + description: Sessions list + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InspectorSession" + post: + tags: [Traffic Inspector] + summary: Start a new recording session + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorSessionStart" + responses: + "201": + description: Session started + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorSession" + + /api/tools/traffic-inspector/sessions/{id}: + get: + tags: [Traffic Inspector] + summary: Get session snapshot (all captured requests) + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: Session with embedded requests + "404": + description: Session not found + patch: + tags: [Traffic Inspector] + summary: Stop or rename a recording session + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InspectorSessionPatch" + responses: + "200": + description: Session updated + delete: + tags: [Traffic Inspector] + summary: Delete a recording session + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "204": + description: Session deleted + + /api/tools/traffic-inspector/sessions/{id}/export.har: + get: + tags: [Traffic Inspector] + summary: Export a recorded session as HAR 1.2 + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + responses: + "200": + description: HAR file for this session + content: + application/json: + schema: + type: object + description: HAR 1.2 format + "404": + description: Session not found + + /api/tools/traffic-inspector/internal/ingest: + post: + tags: [Traffic Inspector] + summary: Internal ingest endpoint for server.cjs passthrough path + description: >- + Accepts a serialized InterceptedRequest from the CJS MITM server for requests + that do not go through TypeScript handlers (e.g., passthrough hosts). Requires + INSPECTOR_INTERNAL_INGEST_TOKEN header. LOCAL_ONLY. + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/InterceptedRequest" + responses: + "204": + description: Ingested + "401": + description: Invalid or missing ingest token + # ─── OpenAPI Spec ────────────────────────────────────────────── /api/openapi/spec: @@ -3160,6 +3776,389 @@ components: $ref: "#/components/schemas/ValidationErrorResponse" schemas: + + # ─── AgentBridge Schemas ──────────────────────────────────────── + + AgentId: + type: string + enum: + - antigravity + - kiro + - copilot + - codex + - cursor + - zed + - claude-code + - open-code + - trae + description: One of the 9 supported IDE agents + + AgentBridgeAgentState: + type: object + description: Per-agent MITM state + properties: + agent_id: + $ref: "#/components/schemas/AgentId" + dns_enabled: + type: boolean + cert_trusted: + type: boolean + setup_completed: + type: boolean + last_started_at: + type: string + format: date-time + nullable: true + last_error: + type: string + nullable: true + + AgentBridgeServerState: + type: object + description: Global AgentBridge MITM server state + properties: + running: + type: boolean + port: + type: integer + example: 443 + certReady: + type: boolean + interceptedCount: + type: integer + activeConnections: + type: integer + lastStartedAt: + type: string + format: date-time + nullable: true + + AgentBridgeServerAction: + type: object + required: [action] + properties: + action: + type: string + enum: [start, stop, restart, trust-cert, regenerate-cert] + + AgentBridgeDnsAction: + type: object + required: [enabled] + properties: + enabled: + type: boolean + + AgentBridgeMappingRow: + type: object + properties: + agent_id: + $ref: "#/components/schemas/AgentId" + source_model: + type: string + example: gpt-4o + target_model: + type: string + example: claude-sonnet-4.7 + updated_at: + type: string + format: date-time + + AgentBridgeMappingPut: + type: object + required: [mappings] + properties: + mappings: + type: array + items: + type: object + required: [source, target] + properties: + source: + type: string + example: gpt-4o + target: + type: string + example: claude-sonnet-4.7 + + AgentBridgeBypassRow: + type: object + properties: + pattern: + type: string + example: "*.bank.*" + source: + type: string + enum: [default, user] + created_at: + type: string + format: date-time + + AgentBridgeBypassUpsert: + type: object + required: [patterns] + properties: + patterns: + type: array + items: + type: string + example: ["*.bank.*", "*.gov.*"] + + AgentBridgeUpstreamCaPost: + type: object + required: [path] + properties: + path: + type: string + description: Absolute path to a PEM file for corporate upstream CA + example: "/etc/ssl/certs/corporate-ca.pem" + + # ─── Traffic Inspector Schemas ────────────────────────────────── + + CaptureSource: + type: string + enum: [agent-bridge, custom-host, http-proxy, system-proxy] + + DetectedKind: + type: string + enum: [llm, app, unknown] + + InterceptedRequest: + type: object + description: A single intercepted HTTP request captured by the Traffic Inspector + required: [id, source, timestamp, method, host, path, requestHeaders, requestSize, responseHeaders, responseSize, status] + properties: + id: + type: string + format: uuid + source: + $ref: "#/components/schemas/CaptureSource" + agent: + $ref: "#/components/schemas/AgentId" + timestamp: + type: string + format: date-time + method: + type: string + example: POST + host: + type: string + example: api.githubcopilot.com + path: + type: string + example: /v1/chat/completions + requestHeaders: + type: object + additionalProperties: + type: string + requestBody: + type: string + nullable: true + description: Masked (secrets replaced with ***) + requestSize: + type: integer + responseHeaders: + type: object + additionalProperties: + type: string + responseBody: + type: string + nullable: true + responseSize: + type: integer + status: + oneOf: + - type: integer + - type: string + enum: [in-flight, error] + proxyLatencyMs: + type: number + nullable: true + upstreamLatencyMs: + type: number + nullable: true + totalLatencyMs: + type: number + nullable: true + error: + type: string + nullable: true + description: Sanitized error message (no stack traces) + sourceModel: + type: string + nullable: true + mappedModel: + type: string + nullable: true + detectedKind: + $ref: "#/components/schemas/DetectedKind" + contextKey: + type: string + nullable: true + description: 12-char SHA-256 hex of the system prompt (for conversation grouping) + example: a3f9c2b1d5e4 + annotation: + type: string + nullable: true + sessionId: + type: string + format: uuid + nullable: true + note: + type: string + nullable: true + description: Informational note (e.g. TLS tunnel metadata) + + InspectorCustomHost: + type: object + properties: + host: + type: string + example: api.openai.com + enabled: + type: boolean + label: + type: string + nullable: true + kind: + type: string + enum: [llm, app, custom] + added_at: + type: string + format: date-time + last_seen_at: + type: string + format: date-time + nullable: true + + InspectorCustomHostCreate: + type: object + required: [host] + properties: + host: + type: string + minLength: 1 + example: my-internal-llm.company.com + enabled: + type: boolean + default: true + label: + type: string + nullable: true + kind: + type: string + enum: [llm, app, custom] + default: custom + + InspectorCaptureModesState: + type: object + properties: + agentBridge: + type: object + properties: + active: + type: boolean + customHosts: + type: object + properties: + active: + type: boolean + count: + type: integer + httpProxy: + type: object + properties: + active: + type: boolean + port: + type: integer + example: 8080 + systemProxy: + type: object + properties: + active: + type: boolean + guardMinutes: + type: integer + + InspectorCaptureModeAction: + type: object + required: [action] + properties: + action: + type: string + enum: [start, stop] + + InspectorSystemProxyAction: + type: object + required: [action] + properties: + action: + type: string + enum: [apply, revert] + port: + type: integer + minimum: 1 + maximum: 65535 + example: 8080 + guardMinutes: + type: integer + minimum: 1 + example: 30 + + InspectorTlsInterceptToggle: + type: object + required: [enabled] + properties: + enabled: + type: boolean + + InspectorAnnotationPut: + type: object + required: [annotation] + properties: + annotation: + type: string + maxLength: 10000 + + InspectorSession: + type: object + properties: + id: + type: string + format: uuid + name: + type: string + nullable: true + started_at: + type: string + format: date-time + ended_at: + type: string + format: date-time + nullable: true + request_count: + type: integer + profile: + type: string + enum: [llm, custom, all] + nullable: true + + InspectorSessionStart: + type: object + properties: + name: + type: string + example: "Antigravity test run #1" + + InspectorSessionPatch: + type: object + required: [action] + properties: + action: + type: string + enum: [stop, rename] + name: + type: string QuotaPool: type: object description: A quota sharing pool — binds a provider connection to allocation rules. diff --git a/scripts/check/check-env-doc-sync.mjs b/scripts/check/check-env-doc-sync.mjs index 3a9742832b..9f86fa8e94 100644 --- a/scripts/check/check-env-doc-sync.mjs +++ b/scripts/check/check-env-doc-sync.mjs @@ -71,6 +71,8 @@ const IGNORE_FROM_CODE = new Set([ "NEXT_RUNTIME", "NODE_TEST_CONTEXT", "VITEST", + // Instruction snippet shown to users (Traffic Inspector HttpProxySnippetCard) — not OmniRoute config. + "NODE_TLS_REJECT_UNAUTHORIZED", // CI providers (set by the runner). "GITHUB_BASE_REF", "GITHUB_BASE_SHA", diff --git a/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx b/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx index fc9a6c6586..d9be06eab9 100644 --- a/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx +++ b/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.tsx @@ -16,7 +16,6 @@ import { ClineToolCard, KiloToolCard, DefaultToolCard, - AntigravityToolCard, CopilotToolCard, CustomCliCard, HermesAgentToolCard, @@ -44,7 +43,6 @@ const GUIDED_TOOL_IDS = new Set([ "amp", "qwen", ]); -const MITM_TOOL_IDS = new Set(["antigravity", "kiro"]); const CUSTOM_TOOL_IDS = new Set(["custom"]); export default function CLIToolsPageClient({ machineId: _machineId }) { @@ -251,7 +249,6 @@ export default function CLIToolsPageClient({ machineId: _machineId }) { if (activeCategory === "all") return true; if (activeCategory === "auto") return AUTO_CONFIGURED_TOOL_IDS.has(toolId); if (activeCategory === "guided") return GUIDED_TOOL_IDS.has(toolId); - if (activeCategory === "mitm") return MITM_TOOL_IDS.has(toolId); if (activeCategory === "custom") return CUSTOM_TOOL_IDS.has(toolId); return true; }); @@ -311,16 +308,6 @@ export default function CLIToolsPageClient({ machineId: _machineId }) { cloudEnabled={cloudEnabled} /> ); - case "antigravity": - return ( - - ); case "cline": return ( ); default: - // #487: Any tool with configType "mitm" should use the MITM card (Start/Stop controls) - if (tool.configType === "mitm") { - return ( - - ); - } return ( { + const timer = setTimeout(() => { + router.replace("/dashboard/tools/agent-bridge"); + }, 2500); + return () => clearTimeout(timer); + }, [router]); + + return ( +
+
+
+ info +

{t("title")}

+
+

{t("message")}

+ +
+
+ ); } diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx new file mode 100644 index 0000000000..91184a9b8e --- /dev/null +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/AgentBridgePageClient.tsx @@ -0,0 +1,236 @@ +"use client"; + +import { useCallback, useState } from "react"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; +import { RiskNoticeBanner } from "./components/RiskNoticeBanner"; +import { AgentBridgeServerCard } from "./components/AgentBridgeServerCard"; +import { AgentList } from "./components/AgentList"; +import { EmptyStateNoProviders } from "./components/EmptyStateNoProviders"; +import { useAgentBridgeState } from "./hooks/useAgentBridgeState"; +import type { MitmTarget } from "@/mitm/types"; +import type { MappingRow } from "./components/ModelMappingTable"; + +// ── Types ──────────────────────────────────────────────────────────────────── + +export interface AgentStateEntry { + agent_id: string; + dns_enabled: boolean; + cert_trusted: boolean; + setup_completed: boolean; + last_started_at: string | null; + last_error: string | null; +} + +export interface AgentBridgeServerState { + running: boolean; + port: number; + certTrusted: boolean; + upstreamCa: string | null; + lastStartedAt: string | null; + activeConns: number; + interceptedCount: number; +} + +export type AgentMappingsMap = Record; + +export interface AgentBridgePageData { + serverState: AgentBridgeServerState; + agentStates: AgentStateEntry[]; + bypassPatterns: string[]; + mappings: AgentMappingsMap; +} + +interface AgentBridgePageClientProps { + initialData: AgentBridgePageData; + targets: MitmTarget[]; + hasProviders: boolean; +} + +// ── Component ──────────────────────────────────────────────────────────────── + +export default function AgentBridgePageClient({ + initialData, + targets, + hasProviders, +}: AgentBridgePageClientProps) { + const t = useTranslations("agentBridge"); + const { data, refresh } = useAgentBridgeState({ initialData }); + const [actionError, setActionError] = useState(null); + + // ── Server actions ──────────────────────────────────────────────────────── + + const handleServerAction = useCallback( + async (action: "start" | "stop" | "restart" | "trust-cert" | "regenerate-cert") => { + setActionError(null); + try { + const res = await fetch("/api/tools/agent-bridge/server", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ action }), + }); + if (!res.ok) { + const err = (await res.json().catch(() => ({ error: { message: `HTTP ${res.status}` } }))) as { + error?: { message?: string }; + }; + throw new Error(err.error?.message ?? `HTTP ${res.status}`); + } + await refresh(); + } catch (err) { + setActionError(err instanceof Error ? err.message : "Unknown error"); + } + }, + [refresh] + ); + + // ── Upstream CA ─────────────────────────────────────────────────────────── + + const handleUpstreamCaSave = useCallback(async (path: string) => { + setActionError(null); + try { + const res = await fetch("/api/tools/agent-bridge/upstream-ca", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ path }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + await refresh(); + } catch (err) { + setActionError(err instanceof Error ? err.message : "Unknown error"); + } + }, [refresh]); + + // ── Bypass list ─────────────────────────────────────────────────────────── + + const handleBypassSave = useCallback(async (patterns: string[]) => { + setActionError(null); + try { + const res = await fetch("/api/tools/agent-bridge/bypass", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ patterns }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + await refresh(); + } catch (err) { + setActionError(err instanceof Error ? err.message : "Unknown error"); + } + }, [refresh]); + + // ── DNS toggle ──────────────────────────────────────────────────────────── + + const handleDnsToggle = useCallback( + async (agentId: string, enabled: boolean) => { + setActionError(null); + try { + const res = await fetch(`/api/tools/agent-bridge/agents/${agentId}/dns`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enabled }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + await refresh(); + } catch (err) { + setActionError(err instanceof Error ? err.message : "Unknown error"); + } + }, + [refresh] + ); + + // ── Mappings save ───────────────────────────────────────────────────────── + + const handleMappingsSave = useCallback( + async (agentId: string, mappings: MappingRow[]) => { + setActionError(null); + try { + const res = await fetch(`/api/tools/agent-bridge/agents/${agentId}/mappings`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mappings }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + await refresh(); + } catch (err) { + setActionError(err instanceof Error ? err.message : "Unknown error"); + } + }, + [refresh] + ); + + // ── Render ──────────────────────────────────────────────────────────────── + + return ( +
+ {/* Risk banner */} + + + {/* Error alert */} + {actionError && ( +
+ error + {actionError} + +
+ )} + + {/* Empty state: no providers */} + {!hasProviders ? ( + + ) : ( + <> + {/* Server card */} + + + {/* Agent list */} + + + {/* Quick links */} +
+

+ {t("quickLinks") || "Quick links"} +

+
+ + dns + {t("quickLinkProviders") || "Configure providers"} + + + network_check + {t("quickLinkInspector") || "View traffic in Traffic Inspector"} + +
+
+ + )} +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentBridgeServerCard.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentBridgeServerCard.tsx new file mode 100644 index 0000000000..b952966eac --- /dev/null +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentBridgeServerCard.tsx @@ -0,0 +1,201 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import { CertStatusIcon } from "./shared/CertStatusIcon"; +import { UpstreamCaField } from "./UpstreamCaField"; +import { BypassListEditor } from "./BypassListEditor"; +import type { AgentBridgeServerState } from "../AgentBridgePageClient"; + +interface AgentBridgeServerCardProps { + serverState: AgentBridgeServerState; + onAction: (action: "start" | "stop" | "restart" | "trust-cert" | "regenerate-cert") => Promise; + onUpstreamCaSave: (path: string) => Promise; + onBypassSave: (patterns: string[]) => Promise; + bypassPatterns: string[]; +} + +/** + * Global server card — status + action buttons + CA field + bypass list. + * Matches plan 11 §3 AgentBridge Server layout. + */ +export function AgentBridgeServerCard({ + serverState, + onAction, + onUpstreamCaSave, + onBypassSave, + bypassPatterns, +}: AgentBridgeServerCardProps) { + const t = useTranslations("agentBridge"); + const [loading, setLoading] = useState(null); + const [expanded, setExpanded] = useState(false); + const [upstreamCa, setUpstreamCa] = useState(serverState.upstreamCa ?? ""); + + const runAction = async (action: "start" | "stop" | "restart" | "trust-cert" | "regenerate-cert") => { + setLoading(action); + try { + await onAction(action); + } finally { + setLoading(null); + } + }; + + const isRunning = serverState.running; + + return ( +
+ {/* Header row */} +
+
+
+ link +
+
+

+ {t("serverCardTitle") || "AgentBridge Server"} + + + {isRunning ? t("statusRunning") || "Running" : t("statusStopped") || "Stopped"} + +

+
+ + {t("serverPort") || "Port"}: {serverState.port ?? 443} + + + {serverState.activeConns !== undefined && ( + + {t("serverConns") || "Connections"}: {serverState.activeConns} + + )} + {serverState.interceptedCount !== undefined && ( + + {t("serverIntercepted") || "Intercepted"}: {serverState.interceptedCount.toLocaleString()} + + )} + {serverState.lastStartedAt && ( + + {t("serverLastStarted") || "Last started"}:{" "} + {new Date(serverState.lastStartedAt).toLocaleTimeString()} + + )} +
+
+
+ + +
+ + {/* Action buttons */} +
+ + + + + + + + + + download + {t("downloadCert") || "Download Cert"} + + + +
+ + {/* Expanded: CA + Bypass */} + {expanded && ( +
+ +
+

+ {t("bypassSectionTitle") || "Bypass List"} +

+

+ {t("bypassSectionDesc") || + "Hosts matching these patterns are tunneled directly (no TLS decryption). Defaults include banks, .gov, and corporate SSO."} +

+ +
+
+ )} +
+ ); +} diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx new file mode 100644 index 0000000000..b3be7f29bf --- /dev/null +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentCard.tsx @@ -0,0 +1,268 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import { AgentIcon } from "./shared/AgentIcon"; +import { DnsStatusBadge } from "./shared/DnsStatusBadge"; +import { ModelMappingTable } from "./ModelMappingTable"; +import { SetupWizard } from "./SetupWizard"; +import { RiskNoticeModal } from "@/shared/components/RiskNoticeModal"; +import type { MitmTarget } from "@/mitm/types"; +import type { AgentStateEntry } from "../AgentBridgePageClient"; +import type { MappingRow } from "./ModelMappingTable"; + +const RISK_STORAGE_KEY_PREFIX = "omniroute-agentbridge-risk-dismissed-"; + +function hasAcceptedRisk(agentId: string): boolean { + try { + return localStorage.getItem(RISK_STORAGE_KEY_PREFIX + agentId) === "true"; + } catch { + return false; + } +} + + +interface AgentCardProps { + target: MitmTarget; + agentState: AgentStateEntry | undefined; + serverRunning: boolean; + mappings: MappingRow[]; + onDnsToggle: (agentId: string, enabled: boolean) => Promise; + onMappingsSave: (agentId: string, mappings: MappingRow[]) => Promise; +} + +/** + * Expandable card for a single IDE agent. + */ +export function AgentCard({ + target, + agentState, + serverRunning, + mappings, + onDnsToggle, + onMappingsSave, +}: AgentCardProps) { + const t = useTranslations("agentBridge"); + const [expanded, setExpanded] = useState(false); + const [wizardOpen, setWizardOpen] = useState(false); + const [dnsLoading, setDnsLoading] = useState(false); + const [riskModalOpen, setRiskModalOpen] = useState(false); + + const dnsEnabled = agentState?.dns_enabled ?? false; + const setupCompleted = agentState?.setup_completed ?? false; + const certTrusted = agentState?.cert_trusted ?? false; + const isInvestigating = target.viability === "investigating"; + + const getStatusBadge = () => { + if (isInvestigating) { + return ( + + search + {t("statusInvestigating") || "Investigating"} + + ); + } + if (setupCompleted && dnsEnabled) { + return ( + + + {t("statusActive") || "Active"} + + ); + } + if (!setupCompleted) { + return ( + + settings + {t("statusSetupRequired") || "Setup required"} + + ); + } + return ( + + warning + {t("statusDnsOff") || "DNS off"} + + ); + }; + + const reallyToggleDns = async (enabled: boolean) => { + setDnsLoading(true); + try { + await onDnsToggle(target.id, enabled); + } finally { + setDnsLoading(false); + } + }; + + const handleDnsToggle = async () => { + const enabling = !dnsEnabled; + if (enabling && !hasAcceptedRisk(target.id)) { + setRiskModalOpen(true); + return; + } + await reallyToggleDns(enabling); + }; + + const handleRiskAccept = async () => { + setRiskModalOpen(false); + await reallyToggleDns(true); + }; + + return ( + <> +
+ {/* Card header */} + + + {/* Expanded content */} + {expanded && ( +
+ {/* Hosts */} +
+

+ {t("agentHosts") || "Intercepted hosts"} +

+
+ {target.hosts.map((h) => ( + + {h} + + ))} +
+
+ + {/* Cert status */} +
+ + {certTrusted ? "verified_user" : "lock_open"} + + {certTrusted + ? t("certTrusted") || "Certificate trusted" + : t("certNotTrusted") || "Certificate not trusted"} +
+ + {/* Investigating notice */} + {isInvestigating && ( +
+

+ {t("investigatingNotice") || + "This agent is under investigation. Hosts and API surface are still being confirmed. Setup will be available once the upstream API is documented."} +

+
+ )} + + {/* Model mappings */} + {!isInvestigating && ( +
+

+ {t("modelMappingsLabel") || "Model mappings"} +

+ +
+ )} + + {/* Action buttons */} +
+ {!isInvestigating && ( + + )} + + {!isInvestigating && ( + + )} + + + network_check + {t("viewTraffic") || "View traffic"} + +
+
+ )} +
+ + {wizardOpen && ( + setWizardOpen(false)} + onDnsToggle={onDnsToggle} + /> + )} + + setRiskModalOpen(false)} + /> + + ); +} diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx new file mode 100644 index 0000000000..d2f97e9bf0 --- /dev/null +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/AgentList.tsx @@ -0,0 +1,142 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; +import { AgentCard } from "./AgentCard"; +import type { MitmTarget } from "@/mitm/types"; +import type { AgentStateEntry, AgentMappingsMap } from "../AgentBridgePageClient"; +import type { MappingRow } from "./ModelMappingTable"; + +interface AgentListProps { + targets: MitmTarget[]; + agentStates: AgentStateEntry[]; + serverRunning: boolean; + mappingsMap: AgentMappingsMap; + onDnsToggle: (agentId: string, enabled: boolean) => Promise; + onMappingsSave: (agentId: string, mappings: MappingRow[]) => Promise; +} + +type SetupFilter = "all" | "active" | "setup-required" | "investigating"; + +/** + * Grid of agent cards with filter + search controls. + * Matches plan 11 §3 IDE Agents section. + */ +export function AgentList({ + targets, + agentStates, + serverRunning, + mappingsMap, + onDnsToggle, + onMappingsSave, +}: AgentListProps) { + const t = useTranslations("agentBridge"); + const [filter, setFilter] = useState("all"); + const [search, setSearch] = useState(""); + + const stateByAgent = Object.fromEntries(agentStates.map((s) => [s.agent_id, s])); + + const filtered = targets.filter((target) => { + // Search filter + if (search) { + const q = search.toLowerCase(); + if ( + !target.name.toLowerCase().includes(q) && + !target.id.toLowerCase().includes(q) && + !target.hosts.some((h) => h.toLowerCase().includes(q)) + ) { + return false; + } + } + + const state = stateByAgent[target.id]; + + // Setup status filter + if (filter === "active") { + return state?.dns_enabled && state?.setup_completed; + } + if (filter === "setup-required") { + return !state?.setup_completed && target.viability !== "investigating"; + } + if (filter === "investigating") { + return target.viability === "investigating"; + } + + return true; + }); + + const filterOptions: { id: SetupFilter; label: string }[] = [ + { id: "all", label: t("filterAll") || "All" }, + { id: "active", label: t("filterActive") || "Active" }, + { id: "setup-required", label: t("filterSetupRequired") || "Setup required" }, + { id: "investigating", label: t("filterInvestigating") || "Investigating" }, + ]; + + return ( +
+ {/* Controls */} +
+

+ {t("agentListTitle") || "IDE Agents"}{" "} + ({targets.length}) +

+ + {/* Filter buttons */} +
+ {filterOptions.map((opt) => ( + + ))} +
+ + {/* Search */} +
+ + search + + setSearch(e.target.value)} + /> +
+
+ + {/* Grid */} +
+ {filtered.length === 0 ? ( +
+ + search_off + +

{t("noAgentsMatch") || "No agents match the current filter"}

+
+ ) : ( + filtered.map((target) => ( + + )) + )} +
+
+ ); +} diff --git a/src/app/(dashboard)/dashboard/tools/agent-bridge/components/BypassListEditor.tsx b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/BypassListEditor.tsx new file mode 100644 index 0000000000..61db7b78fa --- /dev/null +++ b/src/app/(dashboard)/dashboard/tools/agent-bridge/components/BypassListEditor.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useState } from "react"; +import { useTranslations } from "next-intl"; + +const DEFAULT_BYPASS_PATTERNS = [ + "*.bank.*", + "*.gov.*", + "*.okta.com", + "*.auth0.com", +]; + +interface BypassListEditorProps { + patterns: string[]; + onSave: (patterns: string[]) => Promise; +} + +/** + * Textarea / chip editor for user-defined bypass patterns. + * Shows read-only defaults + editable user list. + */ +export function BypassListEditor({ patterns, onSave }: BypassListEditorProps) { + const t = useTranslations("agentBridge"); + const [userInput, setUserInput] = useState(patterns.join("\n")); + const [saving, setSaving] = useState(false); + + const handleSave = async () => { + setSaving(true); + try { + const parsed = userInput + .split("\n") + .map((l) => l.trim()) + .filter(Boolean); + await onSave(parsed); + } finally { + setSaving(false); + } + }; + + return ( +
+
+

+ {t("bypassDefaultsLabel") || "Default bypass patterns (read-only)"} +

+
+ {DEFAULT_BYPASS_PATTERNS.map((p) => ( + + {p} + + ))} +
+
+ +
+ +