Skip to content

feat(client): add opt-in remote hub voice relay - #4056

Draft
ildunari wants to merge 2 commits into
lidge-jun:devfrom
ildunari:feat/remote-hub-voice-relay
Draft

feat(client): add opt-in remote hub voice relay#4056
ildunari wants to merge 2 commits into
lidge-jun:devfrom
ildunari:feat/remote-hub-voice-relay

Conversation

@ildunari

@ildunari ildunari commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add opt-in ocx voice-relay [--port <port>] [--allow-standalone] for Codex clients already connected to an authenticated remote hub. The foreground listener binds only to loopback and uses the existing connection-owned data credential; normal provider routing is unchanged.
  • Forward call creation and call-ID sideband WebSockets to the saved hub. Preserve native caller authentication for Direct mode, replace only hub admission, filter protocol headers and credential-shaped WebSocket query parameters, reject redirects, and bound bodies, frames, buffering, deadlines, and teardown. The process stops on connection/credential drift.
  • Document the two dedicated user-level realtime overrides, verification limits, and selective rollback. No automatic config editing, service installation, pairing, or key rotation is introduced.

Related work: #3361 added the loopback realtime override but explicitly excluded admission-token provider-table mode. #3969 clarified client/proxy voice ownership. This is a separate remote-client transport, not a replacement for those changes. Searches of open and closed voice/realtime PRs did not identify an equivalent connected-client relay.

This touches credential handling and needs maintainer sponsorship and explicit security review before merge. It is intentionally submitted as a contributor draft. No live microphone/speaker session or Windows-native runtime acceptance is claimed; network tests use a synthetic local hub.

Verification

  • bun run typecheck — passed.
  • bun test tests/clients/client-voice-relay.test.ts tests/cli/cli-registry.test.ts tests/cli/cli-capabilities.test.ts tests/cli/cli-help.test.ts tests/test-layout.test.ts tests/test-layout-tooling.test.ts — 70 passed, 0 failed across 6 files.
  • Independent read-only transport review — no remaining P0/P1 findings; 7 relay tests passed, including HTTP admission, binary/text WebSocket forwarding, hostile-origin refusal, ownership drift, and forced teardown.
  • bun run skill:surface:check — passed.
  • bun run privacy:scan — passed.
  • cd docs-site && bun install --frozen-lockfile && bun run build — passed, 425 pages.
  • bun run prepush — passed (typecheck, full repository test runner, privacy scan; visual-client checks skipped automatically because that surface is unchanged).
  • Packaged-artifact smoke test — both HTTP call routes, admission and caller auth, Location, foreign-origin rejection, text/binary WebSocket round trips, and graceful shutdown passed with an isolated synthetic hub.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.

  • I pushed my PR to the latest dev commit.

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the enhancement New feature or request label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • hygiene: unsponsored_surface.

What to do

  • Fix unsponsored_surface — This changes an authentication, workflow, release-automation, or dependency surface. MAINTAINERS.md requires security review for these; ask a maintainer to apply maintainer-sponsored once they have reviewed it. Paths: src/server/auth-cors.ts.
  • Tick all four boxes in the PR description once you're done (currently 0/4).

Review readiness checklist

  • ⬜ All CI tests are green on my local testing.
  • ⬜ I pushed my PR to the latest dev commit.
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

0/4 boxes ticked.

This pull request was already a draft. Its draft status will be preserved after every issue above is resolved.
@ildunari Tick the boxes once your local CI is green, your branch is on the latest dev commit, and every correct Codex and CodeRabbit finding is resolved.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 56 / 80

이 PR은 ocx connect로 이미 원격 허브에 붙어 있는 클라이언트 쪽에서, Codex 음성(call 생성 + realtime sideband WebSocket)만 루프백으로 받아 허브로 넘겨 주는 opt-in 포그라운드 명령 ocx voice-relay를 추가한다. 작성자는 ildunari이고, base는 dev, 헤드 커밋은 987ce6aef(브랜치 feat/remote-hub-voice-relay), DRAFT이며 enhancement 라벨만 있다. 지금 로컬 dev HEAD는 164bd2b36(#4053 wp3), package 2.49.0이다. 이 PR의 merge-base는 9587750ce(#4051 wp2)라서 tip보다 커밋 하나 뒤이고, tip과 겹치는 파일은 scripts/test-layout/layout.jsontests/fixtures/test-layout-expected.json뿐이라 리베이스 충돌은 키 한 줄 삽입 수준이다. types.ts/config.ts 분할 캠페인 파일을 건드리지 않아서 close-don't-rebase 대상이 아니다. #3361/#3968/#3969와 같은 “루프백 Design B 음성” 계열의 원격 클라이언트 빈칸을 채우는 별도 수송이며, 검색상 동일 목적의 열린 voice-relay PR은 없다.

왜 지금 dev에서 의미가 있나. 로컬 Design B(src/codex/inject.ts)는 루프백 openai_base_url과 함께 루트 experimental_realtime_ws_base_url을 마커로 넣어, Codex가 sideband를 api.openai.com으로 직접 가지 않고 프록시 src/server/live.ts로 오게 한다. 그런데 원격 허브 연결은 non-loopback이라 provider-table(legacy) 폼을 쓰고, 그 폼은 x-opencodex-api-key 입학 헤더가 필요하다. inject.ts 주석이 말하듯 sideband WebSocket은 그 입학 토큰을 실을 수 없어서, #3361도 provider-table에는 realtime 키를 의도적으로 안 넣었다. 그래서 원격 클라이언트가 Codex 음성을 켜면, 루프백 주입만으로는 허브 입학이 안 된다. 이 PR은 그 구멍을 “자동 주입”이 아니라 사용자가 직접 돌리는 루프백 전용 릴레이로 메운다. 릴레이는 127.0.0.1만 바인드하고(기본 포트 10111), 저장된 connect 레코드 + owner-matching data credential(src/client/state.tsreadClientConnectionState / assertNoClientDisconnectPending, src/lib/service-secrets.tsreadServiceApiTokenState)을 읽어 HTTP/WS 업스트림에 x-opencodex-api-key만 붙인다. 호출자 Authorization/chatgpt-account-idLIVE_CLIENT_PROTOCOL_HEADERS(src/server/live.ts)는 그대로 통과시키고, 쿼리의 credential-shaped 키는 기존 sanitizeStandaloneRealtimeQuery로 걸러 낸다. 리다이렉트는 거부하고, body/frame/pending/backpressure/deadline/teardown 한도를 둔다. 연결·자격 증명이 바뀌면 모니터가 프로세스를 멈춘다. Codex config.toml을 자동으로 고치지 않고, 문서에 루트 키 두 줄(experimental_realtime_webrtc_call_base_url, experimental_realtime_ws_base_url)을 사용자가 백업 후 손수 넣으라고 한다. 그 webrtc call 키는 로컬 Design B에서는 openai_base_url을 따라가서 inject가 안 넣지만(devlog/_plan/260903_voice_sideband_regression/000_research.md), 원격에서는 일반 라우팅을 건드리지 않으려 음성 URL만 릴레이로 돌리기 때문에 문서에 둘 다 적힌 것이다.

파일 범위는 좁고 일관되다. 새 구현 src/client/voice-relay.ts(약 427줄) + CLI 래퍼 src/cli/voice-relay.ts, 그리고 src/cli/dispatch.ts / registry.ts / help.ts / capabilities.ts와 skill skills/ocx/references/01_management_surface.md(declared capabilities 37→38, 지금 체크아웃 CAPABILITIES 개수 37과 맞음), 가이드 docs-site/.../guides/remote-hub.md의 Remote client voice 절과 codex-integration.md 한 단락, 테스트 tests/clients/client-voice-relay.test.ts(레이아웃 clients 레인 등록 포함)이다. 본문 검증은 typecheck·관련 테스트 70 pass·skill/privacy·docs-site build·prepush·합성 허브 스모크를 주장하고, 실제 마이크/스피커 세션과 Windows-native 수용은 주장하지 않는다. mergeStateStatus는 DRAFT라 BLOCKED이고, 리뷰 readiness 체크리스트 네 칸은 모두 비어 있다. 작성자 스스로 “credential 취급이라 메인테이너 스폰서십·명시적 보안 리뷰 전 merge 금지, contributor draft”라고 적어 두었다. 현재 열어 둔 2.49 슬라이스(#3719 live replay/cache, #3379 selector rename, #3774 native/featured, #3781 authenticated TUN, #3782 client-owned)와는 다른 축이라 그 백로그 closeout을 막지는 않지만, 원격 허브 음성 UX의 실질 구멍이라 점수 56이다. 품질·경계·테스트는 높은 편이고, 즉시 ship 열차에 태우기엔 보안·수동 설정·DRAFT 게이트가 남아 점수를 더 올리지 않았다.

경로 src/client/voice-relay.ts loadVoiceRelayCredential / credentialStillOwned - connect 상태·토큰 핑거프린트·pendingOperation(rotate)까지 fail-closed로 맞춰 두었다. 방향이 맞다.
경로 src/client/voice-relay.ts localAuthorityAllowed - Host/URL이 127.0.0.1|localhost+포트이고 Origin이 없거나 같은 루프백 루트일 때만 통과한다. 외국 Origin·evil Host는 허브 I/O 전에 403. 테스트가 이를 고정한다.
경로 src/server/live.ts 재사용 - parseLiveSidebandTarget / sanitizeStandaloneRealtimeQuery / LIVE_CLIENT_PROTOCOL_HEADERS를 새로 베끼지 않고 가져와 call-id·standalone 게이트·쿼리 denylist가 서버 live와 같은 계약이다. 좋다.
라인 HTTP 응답 헤더 allowlist - Set-Cookie 등 비허용 헤더를 흘리지 않는다. 테스트가 set-cookie null을 확인한다.
경로 docs-site/.../guides/remote-hub.md Remote client voice - 포그라운드·수동 두 키·롤백·“헬스만으로 음성 성공 증명 금지”까지 초등학생도 읽히게 쓰여 있다. 자동 설정/서비스 설치/키 회전을 안 한다는 경계도 분명하다.
경로 PR base vs tip - tip #4053이 레이아웃 JSON에 테스트 엔트리를 더 넣어서, ready 전에 164bd2b36 위로 리베이스가 필요하다(충돌면은 레이아웃 두 파일).
경로 리뷰 readiness 체크리스트 - 네 칸 전부 미체크. DRAFT 유지가 맞고, “ready for review” 전 tip 재푸시·CI·CodeRabbit 정리가 남아 있다.
경로 수동 Codex 설정 - 릴레이가 config를 안 고치는 것은 안전 방향이지만, 사용자가 두 실험 키를 허브 끊긴 뒤에도 남겨 두면 음성만 깨진 채로 남을 수 있다. 문서 롤백은 있으나 자동 복구/경고는 없다.
라인 VOICE_RELAY_WS_IDLE_SECONDS = 120 - Bun.serve idleTimeout 120초다. 말없이 긴 음성 세션이면 조용한 구간에서 소켓이 끊길 수 있다. 합성 허브 테스트는 긴 idle을 덮지 않는다.
경로 docs-site/.../ko/guides/ - 영문 remote-hub/codex-integration만 보강되고 한글 가이드 대응 절은 없다. 필수는 아니지만 원격 허브 문서 대칭이 깨진다.
경로 루프백 신뢰 모델 - 릴레이가 떠 있는 동안 같은 머신의 다른 로컬 프로세스가 127.0.0.1:10111의 허용 음성 라우트로 허브 data credential을 빌려 쓸 수 있다. #3361 루프백 음성 허용과 같은 계열 확장이고, 바인드·Origin 가드로 완화했지만 “루프백=신뢰”를 한 번 더 넓힌다.
경로 검증 한계 - 본문이 인정하듯 라이브 마이크/스피커·Windows-native 수용 증거는 없다. 네트워크 단위·합성 허브만이다.

메인테이너의 판단이 필요한 지점

  • credential을 클라 프로세스 메모리에 올려 음성 라우트에만 붙이는 이 설계를 스폰서할지, 아니면 허브 쪽 admission 개선(또는 다른 원격 음성 설계)을 기다릴지
  • 2.49.x closeout 열차에 넣을지, 보안 리뷰·리베이스·체크리스트 이후에 다음 마이너로 미룰지
  • 나중에 ocx voice-relay가 두 실험 키를 백업/기록/복구까지 할지, 지금처럼 문서-only 수동 설정을 유지할지
  • WS idle 120초를 음성 세션에 충분한지, 더 길게 하거나 heartbeat 정책을 문서화할지
  • 루프백 임의 로컬 프로세스가 릴레이를 쓰는 신뢰 모델을 문서 danger로 더 드러낼지

너의 추천
DRAFT 유지. tip 164bd2b36(#4053) 위로 리베이스해 레이아웃 JSON만 맞추고, 작성자가 요청한 대로 메인테이너 스폰서십 + 명시적 보안 리뷰 전에는 merge하지 말 것. 내용·경계·테스트 방향은 원격 허브 음성 빈칸(#3361이 provider-table에서 뺀 자리)을 정직하게 메우므로 닫지 말고 키운다. types/config 분할과 무관하고 #3361/#3969 중복도 아니다. 지금 2.49 closeout의 열린 다섯 슬라이스보다 급하지 않으니, 보안 리뷰 통과·체크리스트·실기(가능하면 한 번의 실제 음성) 증거 후에 별도 칸으로 올리는 편이 맞다. preview 배포는 필요 없다.

이 댓글은 grok-bot이 작성했습니다

@ildunari
ildunari force-pushed the feat/remote-hub-voice-relay branch from 987ce6a to bbee23d Compare September 8, 2026 20:18
@github-actions github-actions Bot added the intake: hygiene-blocked Deterministic PR hygiene checks failed label Sep 8, 2026
@ildunari

ildunari commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up fix pushed in bbee23d, rebased onto dev ff524ae. The original relay forwarded a proxy admission bearer in Authorization, which the real hub forwarding guard rejected with 401. Both HTTP and WebSocket relay paths now remove connected or recognized proxy admission bearers while retaining native caller OAuth; the hub guard is unchanged semantically through a shared classification helper. Regression reproduced the 401 before the repair. Verification: 9 relay tests and 62 auth tests passed, typecheck and privacy scan passed, final bundled-artifact HTTP/WS smoke passed, and bun run prepush completed with exit 0. Independent focused security review found no P0/P1 blockers. Local-process trust is now explicit in the guide. This remains a draft awaiting maintainer sponsorship/security review; no microphone/speaker acceptance or Windows-native acceptance is claimed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request intake: hygiene-blocked Deterministic PR hygiene checks failed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants