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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions devlog/_plan/260908_voice_relay/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Codex voice relay follow-up

Satisfy-spec HOTL loop, triggered by the maintainer's September voice source comparison request.
Goal: carry only verified OpenCodex-owned improvements and document the client/proxy boundary.
No local product tests, typecheck, build or installs; no release, deployment or user settings changes.
Verification: read pinned upstream source and Aside findings; independent review; final cumulative
remote Cross-platform CI dispatch (all lanes), followed by exact-head merge and fetched dev tree proof.
Local product verification is NOT RUN by explicit user instruction. Git diff/document inspection is
allowed but does not certify runtime behavior. No latency or live audio improvement is claimed.
Stop: audited no-change conclusion, or required corrections landed with fresh remote evidence.
Outcomes: DONE, evidence-backed NOOP, or explicit unmet external gate. No invented time/cost budget;
existing tools/credentials only, bounded individual probes, no new services or installs.
Escalation: unresolved maintainer objection, missing external authority, or unavailable required CI.

## Ordered work phases

1. wp1: source research and audited roadmap (documents only).
2. wp2: scoped relay correction and adjacent regression coverage; depends on wp1.
3. wp3: publish the documented contract, final cumulative CI, and merge; depends on wp2.

Existing owners: `src/server/live.ts`, `src/server/index.ts`, `tests/server/server-live.test.ts`,
`docs-site/src/content/docs/guides/codex-integration.md`, `structure/04_transports-and-sidecars.md`.
No new production abstraction, endpoint or dependency. Preserve preexisting worktree documents.
Manual two-PR chain: relay implementation/tests, then integration documentation. User explicitly
requests final-tip-only product CI, overriding per-layer local/full-suite defaults. Automatic
redundant product CI on these task PRs may be cancelled; it is never counted as passing evidence.
Use merge commits to preserve stack ancestry, retarget the child only after the parent lands,
and recheck the current dev tree before final merge. Required checks remain truthful.

Security working material is kept only in ignored scratch per AGENTS.md. The detailed audited
roadmap resides in `.tmp/voice-0908/010_runtime.md` and `.tmp/voice-0908/020_delivery.md` until
publication of the fix; it is intentionally not copied into this public planning directory.

## Roadmap audit and lock

Independent plan and security audit: PASS, no blockers. The implementation will preserve view
bounds and original frame delivery. Diagnostic replacement-character flags are not evidence of
which peer introduced malformed text. Existing logs are outside this prospective logging change.
The roadmap is locked for wp2; final runtime evidence remains due in wp3, on the cumulative tree.
24 changes: 24 additions & 0 deletions devlog/_plan/260908_voice_relay/001_sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Source comparison

Pinned upstream: openai/codex b01c3986fd2e79b8a477a08d81430f52f22bc0dc (2026-09-07 UTC).
The local corpus is `/Users/jun/Developer/codex`; its 120 and 121 upstream checkouts had older
working heads, so the named commit was fetched without modifying their worktrees.
Comment thread
lidge-jun marked this conversation as resolved.

- https://github.com/openai/codex/commit/1b53f6a44eff890b5169bde8d3bd5b12b8766946:
local voice helper offer/answer, ordered oai-events data channel and UDP/TCP transport.
- https://github.com/openai/codex/commit/b01c3986fd2e79b8a477a08d81430f52f22bc0dc:
feature-gated TUI voice commands, captions, handoff answer delivery and lifecycle cleanup.
- `codex-rs/codex-api/src/endpoint/realtime_call.rs` at the pinned head:
backend JSON and API multipart call creation, Frameless `/live`, AVAS `/realtime/calls`.
- OpenCodex `src/server/live.ts` already implements these call-create and sideband shapes;
`src/server/index.ts` transparently relays frames and bounds pending queues and teardown.
- `tests/server/server-live.test.ts` already covers call creation, protocol headers, pool identity,
sideband joins and frame delivery. Existing implementation is reused, not duplicated.

Fast-tier display text and local audio negotiation do not demonstrate a proxy latency gain.
The TUI merge date does not establish when a desktop binary shipped. Live microphone/audio
verification is outside the automated evidence gathered here.

The Fast-tier metadata commit is 0e0f55fc4ec9308840e54ceba1f1f1dc9547380f,
2026-09-04T00:12:18Z; it changes only `codex-rs/models-manager/models.json`.
It describes the supported service tier, not OpenCodex voice transport performance.
27 changes: 8 additions & 19 deletions src/server/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,39 +86,29 @@ export const LIVE_CLIENT_PROTOCOL_HEADERS = [
*
* When `OCX_LIVE_FRAME_LOG` is set to a file path, every relayed sideband frame appends one
* JSONL record: direction, frame kind, byte length, and whether the payload contains U+FFFD.
* Privacy: full frame payloads are never written — only when U+FFFD is present, a short
* excerpt around the first replacement character is included so the corruption point can be
* attributed (upstream vs relay vs client). Disabled entirely when the env var is unset.
* Privacy: no frame content is written, including excerpts around replacement characters.
* For binary frames, U+FFFD may also be introduced by UTF-8 decoding; the flag alone does not
* identify the source of corruption. Disabled entirely when the env var is unset.
*/
export const LIVE_FRAME_LOG_ENV = "OCX_LIVE_FRAME_LOG";
const LIVE_FRAME_LOG_CONTEXT_CHARS = 24;

function fffdContext(text: string): string | undefined {
const idx = text.indexOf("\uFFFD");
if (idx < 0) return undefined;
const start = Math.max(0, idx - LIVE_FRAME_LOG_CONTEXT_CHARS);
const end = Math.min(text.length, idx + LIVE_FRAME_LOG_CONTEXT_CHARS);
return text.slice(start, end);
}

export function logLiveSidebandFrame(dir: "c2u" | "u2c", data: unknown): void {
const logPath = process.env[LIVE_FRAME_LOG_ENV];
if (!logPath) return;
try {
let kind: "text" | "binary" = "binary";
let bytes = 0;
let context: string | undefined;
let fffd = false;
if (typeof data === "string") {
kind = "text";
bytes = Buffer.byteLength(data);
context = fffdContext(data);
fffd = data.includes("\uFFFD");
} else if (data instanceof ArrayBuffer) {
bytes = data.byteLength;
context = fffdContext(new TextDecoder().decode(new Uint8Array(data)));
fffd = new TextDecoder().decode(new Uint8Array(data)).includes("\uFFFD");
} else if (ArrayBuffer.isView(data)) {
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
bytes = data.byteLength;
context = fffdContext(new TextDecoder().decode(view));
fffd = new TextDecoder().decode(view).includes("\uFFFD");
} else {
return;
}
Expand All @@ -127,8 +117,7 @@ export function logLiveSidebandFrame(dir: "c2u" | "u2c", data: unknown): void {
dir,
kind,
bytes,
fffd: context !== undefined,
...(context !== undefined ? { context } : {}),
fffd,
};
appendFileSync(logPath, `${JSON.stringify(record)}\n`);
} catch {
Expand Down
10 changes: 10 additions & 0 deletions structure/04_transports-and-sidecars.md
Original file line number Diff line number Diff line change
Expand Up @@ -1785,3 +1785,13 @@ The field is omitted when no classified recovery result exists, and existing com
branches that return the original target failure keep that response.
`recovery_unavailable` includes cache/singleflight capacity and does not prove an
upstream request was attempted. No retry or broader envelope acceptance is enabled.

## Voice diagnostic metadata

`src/server/live.ts` owns optional `OCX_LIVE_FRAME_LOG` diagnostics for both sideband directions.
The JSONL schema contains only `ts`, `dir`, `kind`, `bytes`, and `fffd`. It never stores frame
content or transcript excerpts, and logging failures do not affect transparent frame delivery.
Binary detection decodes only the supplied buffer view; malformed UTF-8 can itself produce U+FFFD,
so the flag does not identify the peer responsible for corruption. Existing diagnostic files are
not rewritten. Audio devices, WebRTC media negotiation, captions and spoken handoff delivery remain
client responsibilities.
60 changes: 55 additions & 5 deletions tests/server/server-live.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1237,10 +1237,12 @@ test("sideband relay preserves multibyte UTF-8 frames byte-identically in both d
// The env-gated frame forensic log (OCX_LIVE_FRAME_LOG) records per-frame metadata and
// U+FFFD presence without writing full payloads — the attribution tool for multibyte
// transcript corruption reports.
test("sideband frame log records direction, kind, and U+FFFD context without full payloads", async () => {
test("sideband frame log preserves delivery without recording damaged or clean text", async () => {
const frameLogPath = join(TEST_DIR, "frames.jsonl");
const previousFrameLog = process.env.OCX_LIVE_FRAME_LOG;
process.env.OCX_LIVE_FRAME_LOG = frameLogPath;
const FFFD_TEXT = "가볍게 ��기핼봐요";
const received: string[] = [];

const upstream = Bun.serve({
port: 0,
Expand Down Expand Up @@ -1293,7 +1295,8 @@ test("sideband frame log records direction, kind, and U+FFFD context without ful
client.addEventListener("open", () => {
client.send("clean-frame");
});
client.addEventListener("message", () => {
client.addEventListener("message", event => {
received.push(String(event.data));
acks += 1;
if (acks >= 2) {
clearTimeout(timer);
Expand All @@ -1316,23 +1319,70 @@ test("sideband frame log records direction, kind, and U+FFFD context without ful
expect(u2cFffd).toBeDefined();
expect(u2cFffd.kind).toBe("text");
expect(u2cFffd.bytes).toBeGreaterThan(0);
expect(u2cFffd.context).toContain("�");
expect(received).toContain(FFFD_TEXT);
expect(c2uClean).toBeDefined();
expect(c2uClean.fffd).toBe(false);
// Full payloads must never be logged — only short FFFD context excerpts.
// Even a short damaged transcript must not be persisted as diagnostic context.
for (const line of lines) {
expect(Object.keys(line).sort()).toEqual(["bytes", "dir", "fffd", "kind", "ts"]);
expect(JSON.stringify(line)).not.toContain("clean-frame");
expect(JSON.stringify(line)).not.toContain(FFFD_TEXT);
}

client.close();
} finally {
delete process.env.OCX_LIVE_FRAME_LOG;
if (previousFrameLog === undefined) delete process.env.OCX_LIVE_FRAME_LOG;
else process.env.OCX_LIVE_FRAME_LOG = previousFrameLog;
globalThis.WebSocket = RealWebSocket;
await server.stop(true);
await upstream.stop(true);
}
});

test("frame diagnostics retain only metadata for text, binary, and bounded views", async () => {
const { logLiveSidebandFrame } = await import("../../src/server/live");
const previousFrameLog = process.env.OCX_LIVE_FRAME_LOG;
const frameLogPath = join(TEST_DIR, "frame-metadata.jsonl");
const damagedText = "private-voice-�";
const encoded = new TextEncoder().encode(damagedText);
const padded = new TextEncoder().encode("�safe�");
const frames: Array<{ data: unknown; kind: string; bytes: number; fffd: boolean }> = [
{ data: damagedText, kind: "text", bytes: 17, fffd: true },
{ data: encoded.buffer, kind: "binary", bytes: 17, fffd: true },
{ data: Buffer.from(encoded), kind: "binary", bytes: 17, fffd: true },
// Replacement characters outside this view must not affect the flag or byte count.
{ data: new Uint8Array(padded.buffer, 3, 4), kind: "binary", bytes: 4, fffd: false },
{ data: new DataView(padded.buffer, 3, 4), kind: "binary", bytes: 4, fffd: false },
{ data: "한글", kind: "text", bytes: 6, fffd: false },
{ data: new Uint8Array([0xff]), kind: "binary", bytes: 1, fffd: true },
];
try {
process.env.OCX_LIVE_FRAME_LOG = frameLogPath;
for (const frame of frames) logLiveSidebandFrame("u2c", frame.data);
logLiveSidebandFrame("c2u", { privateText: damagedText });
const raw = readFileSync(frameLogPath, "utf8");
const records = raw.trim().split("\n").map(line => JSON.parse(line));
expect(records).toHaveLength(frames.length);
records.forEach((record, index) => {
const expected = frames[index]!;
expect(record).toEqual({
ts: expect.any(String), dir: "u2c", kind: expected.kind,
bytes: expected.bytes, fffd: expected.fffd,
});
expect(Number.isNaN(Date.parse(record.ts))).toBe(false);
});
for (const content of [damagedText, "safe", "한글", "�"]) expect(raw).not.toContain(content);
delete process.env.OCX_LIVE_FRAME_LOG;
logLiveSidebandFrame("c2u", damagedText);
expect(readFileSync(frameLogPath, "utf8")).toBe(raw);
process.env.OCX_LIVE_FRAME_LOG = TEST_DIR;
expect(() => logLiveSidebandFrame("c2u", damagedText)).not.toThrow();
} finally {
if (previousFrameLog === undefined) delete process.env.OCX_LIVE_FRAME_LOG;
else process.env.OCX_LIVE_FRAME_LOG = previousFrameLog;
}
});

// ── /readyz: per-server readiness gate ────────────────────────────────────────
// /healthz remains the immediate liveness signal (with only bounded capability
// metadata); /readyz is the stricter gate that reflects the post-startup Codex sync
Expand Down
Loading