Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0b11f14
fix(backend): Agent Bridge DNS start succeeds but UI does not update …
infinit-X Jul 27, 2026
0103ede
fix(backend): diagnose shows DNS ✓ for non-Antigravity agents (#8656 …
infinit-X Jul 27, 2026
3722333
fix(frontend): agent cards show cert trusted status correctly (#8656 …
infinit-X Jul 27, 2026
00b5f7f
feat(frontend): Setup Wizard auto-detects models from intercepted tra…
infinit-X Jul 27, 2026
80138cb
fix(mitm): capture ALL agent traffic for Traffic Inspector & model au…
infinit-X Jul 29, 2026
e189f45
fix(mitm): fix ingest 400 error & Next.js 15 async params (#8656 foll…
infinit-X Jul 29, 2026
79afa9b
fix(frontend): Setup Wizard merges detected models with existing mapp…
infinit-X Jul 30, 2026
61a06ba
fix(backend): sync agent_bridge_mappings to MITM proxy via key_value …
infinit-X Jul 30, 2026
97cb4be
fix(stream): suppress [DONE] marker for Antigravity SSE responses (#8…
infinit-X Jul 31, 2026
9f8c547
fix(wizard): add delay for parent state refresh before closing modal
infinit-X Jul 31, 2026
c66b780
ci: add lightweight standalone build workflow
infinit-X Jul 31, 2026
554ffce
ci: allow build workflow to trigger on all branches
infinit-X Jul 31, 2026
f862019
fix(ci): cap Node memory at 4GB and use webpack fallback to prevent r…
infinit-X Jul 31, 2026
e8ae867
ci: add 10GB swap space to workflow to eliminate runner OOM shutdown
infinit-X Jul 31, 2026
c1aeed0
ci: replace marketplace swap action with native Linux swapon step
infinit-X Jul 31, 2026
b522a25
fix(ci): make swap creation idempotent and target /mnt ephemeral disk
infinit-X Jul 31, 2026
ec457ef
ci: raise Node V8 max-old-space-size to 12GB using 17GB virtual memory
infinit-X Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Build App

on:
workflow_dispatch:
push:
branches: ["**"]

permissions:
contents: read

jobs:
build:
name: Fast Production Build
runs-on: ubuntu-latest
steps:
- name: Expand Virtual Memory (Native 10GB Swap)
run: |
sudo swapoff -a || true
sudo rm -f /mnt/swapfile /swapfile
sudo fallocate -l 10G /mnt/swapfile || sudo dd if=/dev/zero of=/mnt/swapfile bs=1M count=10240
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile
free -h

- name: Checkout repository
uses: actions/checkout@v7
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm

- name: Install dependencies
run: npm ci

- name: Build Next.js app & CLI bundle
run: |
npm run build:release
env:
NODE_OPTIONS: "--max-old-space-size=12288"
OMNIROUTE_BUILD_MEMORY_MB: "12288"
OMNIROUTE_USE_TURBOPACK: "1"

- name: Archive build outputs
run: |
tar -czf omniroute-build.tar.gz .build dist

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: omniroute-build
path: omniroute-build.tar.gz
retention-days: 7
17 changes: 14 additions & 3 deletions open-sse/utils/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -730,11 +730,22 @@ export function createSSEStream(options: StreamOptions = {}) {
? clientResponseFormat === FORMATS.CLAUDE
: sourceFormat === FORMATS.CLAUDE) === true;

// Antigravity/cloudcode streams terminate naturally on their last
// `data: {"response":{...}}` event, not on a `[DONE]` marker. Emitting
// `[DONE]` to the Antigravity IDE causes a protobuf parse failure
// (proto: syntax error (line 1:1): unexpected token [) because the
// Go binary's protobuf deserializer receives `[DONE]` as input.
const clientExpectsAntigravityStream =
(mode === STREAM_MODE.PASSTHROUGH
? clientResponseFormat === FORMATS.ANTIGRAVITY
: sourceFormat === FORMATS.ANTIGRAVITY) === true;

// Single source of truth for the [DONE] decision, used at both emission
// sites below. Only OpenAI Chat Completions clients expect [DONE];
// Responses API and Anthropic SSE terminate on their own protocol events
// (response.completed / message_stop respectively).
const shouldEmitDoneTerminator = !clientExpectsResponsesStream && !clientExpectsClaudeStream;
// Responses API, Anthropic SSE, and Antigravity/cloudcode terminate on
// their own protocol events (response.completed / message_stop / last
// response candidate respectively).
const shouldEmitDoneTerminator = !clientExpectsResponsesStream && !clientExpectsClaudeStream && !clientExpectsAntigravityStream;

let buffer = "";
let usage: UsageTokenRecord | null = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ export default function AgentBridgePageClient({
targets={targets}
agentStates={data.agentStates}
serverRunning={data.serverState.running}
serverState={data.serverState}
mappingsMap={data.mappings}
onDnsToggle={handleDnsToggle}
onMappingsSave={handleMappingsSave}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ModelMappingTable } from "./ModelMappingTable";
import { SetupWizard } from "./SetupWizard";
import { RiskNoticeModal } from "@/shared/components/RiskNoticeModal";
import type { MitmTargetView } from "@/mitm/types";
import type { AgentStateEntry } from "../AgentBridgePageClient";
import type { AgentStateEntry, AgentBridgeServerState } from "../AgentBridgePageClient";
import type { MappingRow } from "./ModelMappingTable";

const RISK_STORAGE_KEY_PREFIX = "omniroute-agentbridge-risk-dismissed-";
Expand All @@ -26,6 +26,7 @@ interface AgentCardProps {
target: MitmTargetView;
agentState: AgentStateEntry | undefined;
serverRunning: boolean;
serverState: AgentBridgeServerState;
mappings: MappingRow[];
onDnsToggle: (agentId: string, enabled: boolean) => Promise<void>;
onMappingsSave: (agentId: string, mappings: MappingRow[]) => Promise<void>;
Expand All @@ -38,6 +39,7 @@ export function AgentCard({
target,
agentState,
serverRunning,
serverState,
mappings,
onDnsToggle,
onMappingsSave,
Expand All @@ -50,7 +52,9 @@ export function AgentCard({

const dnsEnabled = agentState?.dns_enabled ?? false;
const setupCompleted = agentState?.setup_completed ?? false;
const certTrusted = agentState?.cert_trusted ?? false;
// Fix #8656 Issue A: Use server-level cert trust as fallback
// (one server cert applies to all agents; agentState.cert_trusted is never written to DB)
const certTrusted = agentState?.cert_trusted ?? serverState.certTrusted ?? false;
const isInvestigating = target.viability === "investigating";

const getStatusBadge = () => {
Expand Down Expand Up @@ -250,8 +254,11 @@ export function AgentCard({
target={target}
agentState={agentState}
serverRunning={serverRunning}
serverState={serverState}
currentMappings={mappings}
onClose={() => setWizardOpen(false)}
onDnsToggle={onDnsToggle}
onMappingsSave={onMappingsSave}
/>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useState } from "react";
import { useTranslations } from "next-intl";
import { AgentCard } from "./AgentCard";
import type { MitmTargetView } from "@/mitm/types";
import type { AgentStateEntry, AgentMappingsMap } from "../AgentBridgePageClient";
import type { AgentStateEntry, AgentMappingsMap, AgentBridgeServerState } from "../AgentBridgePageClient";
import type { MappingRow } from "./ModelMappingTable";

interface AgentListProps {
targets: MitmTargetView[];
agentStates: AgentStateEntry[];
serverRunning: boolean;
serverState: AgentBridgeServerState;
mappingsMap: AgentMappingsMap;
onDnsToggle: (agentId: string, enabled: boolean) => Promise<void>;
onMappingsSave: (agentId: string, mappings: MappingRow[]) => Promise<void>;
Expand All @@ -26,6 +27,7 @@ export function AgentList({
targets,
agentStates,
serverRunning,
serverState,
mappingsMap,
onDnsToggle,
onMappingsSave,
Expand Down Expand Up @@ -130,6 +132,7 @@ export function AgentList({
target={target}
agentState={stateByAgent[target.id]}
serverRunning={serverRunning}
serverState={serverState}
mappings={mappingsMap[target.id] ?? []}
onDnsToggle={onDnsToggle}
onMappingsSave={onMappingsSave}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ export function ModelMappingTable({ agentId, mappings, onSave }: ModelMappingTab
setSelectorOpen(null);
};

const addMapping = () => {
setRows((prev) => [...prev, { source: "", target: "" }]);
};

const removeMapping = (index: number) => {
setRows((prev) => prev.filter((_, i) => i !== index));
};

const updateSource = (index: number, source: string) => {
setRows((prev) => prev.map((r, i) => (i === index ? { ...r, source } : r)));
};

const handleSave = async () => {
setSaving(true);
try {
Expand All @@ -38,66 +50,101 @@ export function ModelMappingTable({ agentId, mappings, onSave }: ModelMappingTab
}
};

if (rows.length === 0) {
return (
<p className="text-xs text-text-muted italic">
{t("noMappings") || "No model mappings configured. Run setup wizard to auto-detect models."}
</p>
);
}

return (
<div className="flex flex-col gap-3">
<div className="rounded-lg border border-border/40 overflow-hidden bg-surface">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border/40 bg-surface/60">
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
{t("sourceModel") || "Source model (agent native)"}
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
{t("targetModel") || "Target model (OmniRoute)"}
</th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i} className="border-b border-border/20 last:border-0">
<td className="px-3 py-2">
<span className="font-mono text-xs text-text-muted">{row.source}</span>
</td>
<td className="px-3 py-2">
<button
type="button"
onClick={() => setSelectorOpen(i)}
className="inline-flex items-center gap-1.5 rounded-lg border border-border/40 bg-card px-2.5 py-1 text-xs hover:bg-surface transition-colors font-mono"
>
{row.target || (
<span className="text-text-muted italic">
{t("selectModel") || "Select…"}
</span>
)}
<span className="material-symbols-outlined text-[12px] text-text-muted">
expand_more
</span>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{rows.length === 0 ? (
<div className="rounded-lg border border-border/40 bg-surface/30 px-4 py-6 text-center">
<p className="text-xs text-text-muted mb-3">
{t("noMappingsDesc") || "No model mappings configured yet. Add mappings to route agent requests through OmniRoute."}
</p>
<button
type="button"
onClick={addMapping}
className="inline-flex items-center gap-1.5 rounded-lg bg-primary/10 text-primary px-3 py-1.5 text-xs font-medium hover:bg-primary/20 transition-colors"
>
<span className="material-symbols-outlined text-[14px]">add</span>
{t("addMapping") || "Add mapping"}
</button>
</div>
) : (
<>
<div className="rounded-lg border border-border/40 overflow-hidden bg-surface">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-border/40 bg-surface/60">
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
{t("sourceModel") || "Source model (agent native)"}
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-text-muted">
{t("targetModel") || "Target model (OmniRoute)"}
</th>
<th className="px-3 py-2 w-12"></th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i} className="border-b border-border/20 last:border-0">
<td className="px-3 py-2">
<input
type="text"
value={row.source}
onChange={(e) => updateSource(i, e.target.value)}
placeholder="e.g., gpt-4"
className="w-full rounded border border-border/40 bg-card px-2 py-1 text-xs font-mono focus:outline-none focus:ring-2 focus:ring-primary/50"
/>
</td>
<td className="px-3 py-2">
<button
type="button"
onClick={() => setSelectorOpen(i)}
className="inline-flex items-center gap-1.5 rounded-lg border border-border/40 bg-card px-2.5 py-1 text-xs hover:bg-surface transition-colors font-mono w-full justify-between"
>
{row.target || (
<span className="text-text-muted italic">
{t("selectModel") || "Select…"}
</span>
)}
<span className="material-symbols-outlined text-[12px] text-text-muted">
expand_more
</span>
</button>
</td>
<td className="px-3 py-2">
<button
type="button"
onClick={() => removeMapping(i)}
className="text-text-muted hover:text-red-500 transition-colors"
aria-label="Remove mapping"
>
<span className="material-symbols-outlined text-[16px]">delete</span>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>

<div className="flex justify-end">
<button
type="button"
onClick={handleSave}
disabled={saving}
className="rounded-lg bg-primary/10 text-primary px-4 py-1.5 text-sm font-medium hover:bg-primary/20 transition-colors disabled:opacity-50"
>
{saving ? t("saving") || "Saving…" : t("saveMappings") || "Save mappings"}
</button>
</div>
<div className="flex justify-between items-center">
<button
type="button"
onClick={addMapping}
className="inline-flex items-center gap-1.5 rounded-lg border border-border/40 bg-card px-3 py-1.5 text-xs font-medium hover:bg-surface transition-colors"
>
<span className="material-symbols-outlined text-[14px]">add</span>
{t("addMapping") || "Add mapping"}
</button>
<button
type="button"
onClick={handleSave}
disabled={saving}
className="rounded-lg bg-primary/10 text-primary px-4 py-1.5 text-sm font-medium hover:bg-primary/20 transition-colors disabled:opacity-50"
>
{saving ? t("saving") || "Saving…" : t("saveMappings") || "Save mappings"}
</button>
</div>
</>
)}

{selectorOpen !== null && (
<ModelSelectorModal
Expand Down
Loading