File size: 6,313 Bytes
34810d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | // Proxy capture coverage helpers summarize which network calls were captured.
import { resolveDebugProxySettings, type DebugProxySettings } from "./env.js";
import type { CaptureProtocol } from "./types.js";
// Debug-proxy coverage records which transport seams are fully captured versus
// merely routed through a proxy, so operators know where packet evidence is weak.
export type DebugProxyCoverageStatus = "captured" | "proxy-only" | "uncovered";
export type DebugProxyCoverageEntry = {
id: string;
label: string;
modulePath: string;
protocols: CaptureProtocol[];
status: DebugProxyCoverageStatus;
notes: string;
};
export type DebugProxyCoverageSummary = {
total: number;
captured: number;
proxyOnly: number;
uncovered: number;
};
const DEBUG_PROXY_COVERAGE_ENTRIES: readonly DebugProxyCoverageEntry[] = [
{
id: "provider-transport-fetch",
label: "Provider HTTP transport",
modulePath: "src/agents/provider-transport-fetch.ts",
protocols: ["http", "https", "sse"],
status: "captured",
notes:
"Central provider fetch seam routes through explicit proxy overrides and records request/response payloads.",
},
{
id: "discord-rest",
label: "Discord REST monitor fetch",
modulePath: "extensions/discord/monitor/rest-fetch.ts",
protocols: ["http", "https"],
status: "captured",
notes: "Discord monitor REST calls inherit the debug proxy and record HTTP exchanges.",
},
{
id: "discord-gateway",
label: "Discord gateway monitor",
modulePath: "extensions/discord/monitor/gateway-plugin.ts",
protocols: ["https", "wss"],
status: "captured",
notes:
"Gateway metadata fetches and websocket lifecycle events are captured for monitor traffic.",
},
{
id: "telegram-fetch",
label: "Telegram fetch resolver",
modulePath: "extensions/telegram/fetch.ts",
protocols: ["http", "https"],
status: "captured",
notes:
"Telegram API fetch fallback picks up debug proxy env and records outbound/inbound exchanges.",
},
{
id: "mattermost-ws",
label: "Mattermost monitor websocket",
modulePath: "extensions/mattermost/mattermost/monitor-websocket.ts",
protocols: ["ws", "wss"],
status: "captured",
notes: "Mattermost websocket monitor uses the debug proxy agent and records frame activity.",
},
{
id: "openai-realtime-voice",
label: "OpenAI realtime voice bridge",
modulePath: "extensions/openai/realtime-voice-provider.ts",
protocols: ["wss"],
status: "captured",
notes:
"Realtime voice bridge now routes through the debug proxy agent and records websocket frames.",
},
{
id: "openai-realtime-transcription",
label: "OpenAI realtime transcription",
modulePath: "extensions/openai/realtime-transcription-provider.ts",
protocols: ["wss"],
status: "captured",
notes:
"Realtime transcription sessions now route through the debug proxy agent and record websocket frames.",
},
{
id: "openai-tts",
label: "OpenAI text-to-speech",
modulePath: "extensions/openai/tts.ts",
protocols: ["https"],
status: "captured",
notes:
"Direct OpenAI TTS fetches record request/response payloads while inheriting proxy env routing.",
},
{
id: "microsoft-voices",
label: "Microsoft voice discovery",
modulePath: "extensions/microsoft/speech-provider.ts",
protocols: ["https"],
status: "captured",
notes:
"Microsoft voice listing fetches record HTTP exchanges and follow process-wide proxy routing.",
},
{
id: "feishu-client-http",
label: "Feishu SDK HTTP client",
modulePath: "extensions/feishu/client.ts",
protocols: ["https"],
status: "proxy-only",
notes:
"Feishu SDK traffic can inherit ambient proxying, but decrypted request/response capture is not yet wired at the SDK seam.",
},
{
id: "feishu-client-ws",
label: "Feishu SDK websocket client",
modulePath: "extensions/feishu/client.ts",
protocols: ["wss"],
status: "proxy-only",
notes:
"Feishu websocket creation can inherit ambient proxying, but websocket frame capture is not yet wired.",
},
];
let warnedCoverageSessionKey: string | null = null;
function listDebugProxyCoverageEntries(): DebugProxyCoverageEntry[] {
// Return copies because callers may render/sort/filter entries for CLI output.
return DEBUG_PROXY_COVERAGE_ENTRIES.map((entry) => ({
...entry,
protocols: [...entry.protocols],
}));
}
function summarizeDebugProxyCoverage(
entries: readonly DebugProxyCoverageEntry[] = DEBUG_PROXY_COVERAGE_ENTRIES,
): DebugProxyCoverageSummary {
let captured = 0;
let proxyOnly = 0;
let uncovered = 0;
for (const entry of entries) {
if (entry.status === "captured") {
captured += 1;
continue;
}
if (entry.status === "proxy-only") {
proxyOnly += 1;
continue;
}
uncovered += 1;
}
return {
total: entries.length,
captured,
proxyOnly,
uncovered,
};
}
export function buildDebugProxyCoverageReport() {
const entries = listDebugProxyCoverageEntries();
return {
summary: summarizeDebugProxyCoverage(entries),
entries,
};
}
export function maybeWarnAboutDebugProxyCoverage(
settings: DebugProxySettings = resolveDebugProxySettings(),
warn: (message: string) => void = (message) => console.warn(message),
): void {
if (!settings.enabled || !settings.required) {
return;
}
const sessionKey = `${settings.sessionId}:${settings.proxyUrl ?? ""}`;
// Warn once per capture session/proxy URL; the gaps are static enough that
// repeating them during a run only adds noise.
if (warnedCoverageSessionKey === sessionKey) {
return;
}
warnedCoverageSessionKey = sessionKey;
const report = buildDebugProxyCoverageReport();
const { summary } = report;
const partial = report.entries.filter((entry) => entry.status !== "captured");
if (partial.length === 0) {
return;
}
warn(
`[openclaw proxy] debug proxy coverage: ${summary.captured}/${summary.total} captured, ${summary.proxyOnly} proxy-only, ${summary.uncovered} uncovered.`,
);
warn(
`[openclaw proxy] remaining gaps: ${partial.map((entry) => entry.id).join(", ")}. Run \`openclaw proxy coverage\` for details.`,
);
}
|