From 7c94eefb6d57151a2f089eed780792295aedadd0 Mon Sep 17 00:00:00 2001 From: Godopu Date: Thu, 16 Jul 2026 23:34:23 +0900 Subject: [PATCH] docs: add development plan for MAM Web PTY WebSocket Bridge architecture --- docs/MAM_WEB_PTY_BRIDGE_PLAN.md | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/MAM_WEB_PTY_BRIDGE_PLAN.md diff --git a/docs/MAM_WEB_PTY_BRIDGE_PLAN.md b/docs/MAM_WEB_PTY_BRIDGE_PLAN.md new file mode 100644 index 0000000..18b5829 --- /dev/null +++ b/docs/MAM_WEB_PTY_BRIDGE_PLAN.md @@ -0,0 +1,56 @@ +# ๐Ÿ“‘ MAM Web PTY WebSocket Bridge ๊ฐœ๋ฐœ ๊ณ„ํš์„œ + +์ด ๋ฌธ์„œ๋Š” ๋ธŒ๋ผ์šฐ์ € ํ™˜๊ฒฝ์—์„œ POSIX ๊ฐ€์ƒ ํ„ฐ๋ฏธ๋„(PTY) FFI์— ์ง์ ‘ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†๋Š” ์ƒŒ๋“œ๋ฐ•์Šค ์ œ์•ฝ์„ ๊ทน๋ณตํ•˜๊ณ , `apps/mam_web` ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ†ตํ•ด ์„ธ์…˜ attach ๋ฐ ์ œ์–ด ๊ธฐ๋Šฅ์„ ์˜จ์ „ํžˆ ๊ตฌ๋™ํ•˜๊ธฐ ์œ„ํ•œ **WebSocket PTY ๋ธŒ๋ฆฟ์ง€ ์•„ํ‚คํ…์ฒ˜ ๋ฐ ๊ตฌํ˜„ ๊ณ„ํš**์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 1. ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š” (Architecture Overview) + +๋ธŒ๋ผ์šฐ์ € ๋‹จ๋…์œผ๋กœ๋Š” ๋กœ์ปฌ ๋ฆฌ๋ˆ…์Šค์˜ ์‹œ์Šคํ…œ ํ˜ธ์ถœ(`fork()`, `execvp()`, `fcntl()`, `waitpid()`)์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ๋กœ์ปฌ ๋ฐ๋ชฌ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ์ดˆ๊ฒฝ๋Ÿ‰ WebSocket ํ”„๋ก์‹œ ๋ธŒ๋ฆฟ์ง€ ์„œ๋ฒ„๋ฅผ ๊ฒฝ์œ ํ•˜์—ฌ ์–‘๋ฐฉํ–ฅ ํ„ฐ๋ฏธ๋„ ์ŠคํŠธ๋ฆผ์„ ์ค‘๊ณ„ํ•ฉ๋‹ˆ๋‹ค. + +```mermaid +graph TD + Client[\"MAM Web Client (Browser)\" - apps/mam_web] + Bridge[\"Shelf WebSocket Bridge (Daemon)\" - packages/mam_bridge] + PTY[\"POSIX PtySession (FFI)\" - packages/mam_pty] + Tmux[\"tmux -L multi-agent-mux attach\" - Local process] + + Client -- \"1. ws://localhost:8080/attach?session=demo\" --> Bridge + Bridge -- \"2. spawn PTY (fork-safe FFI)\" --> PTY + PTY -- \"3. dup2 redirect\" --> Tmux + Client -- \"4. Send inputs (keystrokes)\" --> Bridge + Bridge -- \"5. pty.writeString()\" --> PTY + PTY -- \"6. Stream stdout bytes\" --> Bridge + Bridge -- \"7. WebSocket Frame (text/binary)" --> Client +``` + +--- + +## 2. ์ƒ์„ธ ๊ตฌํ˜„ ์‚ฌ์–‘ (Implementation Details) + +### 2.1 PTY WebSocket Bridge ๋ฐ๋ชฌ (`packages/mam_bridge`) +* **์—ญํ• **: `shelf` ๋ฐ `shelf_web_socket`์„ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ์ปฌ ๋ฃจํ”„๋ฐฑ(`127.0.0.1`) ๋˜๋Š” ์ง€์ • ๋ฐ”์ธ๋”ฉ ํฌํŠธ์—์„œ ๋Œ€๊ธฐํ•˜๋Š” HTTP/WebSocket ์ค‘๊ณ„ ์„œ๋ฒ„ ๊ตฌํ˜„. +* **์ ‘์† ์—”๋“œํฌ์ธํŠธ**: `/ws/attach?session=&server=` +* **๋™์ž‘ ์‹œํ€€์Šค**: + 1. WebSocket ํ•ธ๋“œ์…ฐ์ดํฌ ์š”์ฒญ์ด ๋„๋‹ฌํ•˜๋ฉด ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ(`session`, `server`)๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. + 2. `packages/mam_pty`์˜ `PtySession.start('tmux', ['-L', server, 'attach', '-t', session])`์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋น„๋™๊ธฐ ์Šคํฐํ•ฉ๋‹ˆ๋‹ค. + 3. `PtySession.stdout` ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์„ ์ˆ˜์‹ ํ•˜๋Š” ์ฆ‰์‹œ WebSocket binary/text ํ”„๋ ˆ์ž„์œผ๋กœ ๋ž˜ํ•‘ํ•ด ์›น ๋ธŒ๋ผ์šฐ์ €๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. + 4. ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ WebSocket ์ฑ„๋„๋กœ ๋ณด๋‚ธ ํ‚ค๋ณด๋“œ/๋งˆ์šฐ์Šค ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋Š” `PtySession.writeString()`์œผ๋กœ ํฌ์›Œ๋”ฉํ•ฉ๋‹ˆ๋‹ค. + 5. WebSocket ์—ฐ๊ฒฐ์ด ๋Š์–ด์ง€๊ฑฐ๋‚˜ ๋ธŒ๋ผ์šฐ์ € ํƒญ์ด ๋‹ซํžˆ๋ฉด `PtySession.close()`๋ฅผ ์ฆ‰์‹œ ํ˜ธ์ถœํ•˜์—ฌ ์ž์‹ tmux ํ”„๋กœ์„ธ์Šค๋ฅผ `waitpid()`๋กœ ์†Œ๊ฑฐ(reap)ํ•˜๊ณ  PTY FFI ํ•ธ๋“ค์„ ์›์ž์ ์œผ๋กœ ๋‹ซ์Šต๋‹ˆ๋‹ค. + +### 2.2 MAM Web ํด๋ผ์ด์–ธํŠธ UI (`apps/mam_web`) +* **์—ญํ• **: ๋Œ€์‹œ๋ณด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ฐ WebSocket ๊ธฐ๋ฐ˜ ํ„ฐ๋ฏธ๋„ ์œ„์ ฏ ์ด์‹. +* **์ƒํƒœ ๊ด€๋ฆฌ**: ๊ธฐ์กด M1/M2์˜ `StatusRepository` ๋ฐ Riverpod ํด๋ง ์ŠคํŠธ๋ฆผ์„ ์›น ํด๋ผ์ด์–ธํŠธ ์‚ฌ์–‘์œผ๋กœ ๋™์ผํ•˜๊ฒŒ ๊ณต์œ ํ•˜์—ฌ ๋Œ€์‹œ๋ณด๋“œ ๊ทธ๋ฆฌ๋“œ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. +* **`TerminalPane` ์›น ์ „์šฉ ์ปดํฌ๋„ŒํŠธ**: + * PTY FFI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ž„ํฌํŠธํ•˜์ง€ ์•Š๊ณ  `package:web_socket_channel/web_socket_channel.dart`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐฑ์—”๋“œ ๋ธŒ๋ฆฟ์ง€ ์„œ๋ฒ„์— ์†Œ์ผ“์„ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. + * ์†Œ์ผ“ ์ŠคํŠธ๋ฆผ(`channel.stream`)์„ `xterm` v4.0.0 `Terminal`๋กœ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค. + * `Terminal.onOutput` ์ฝœ๋ฐฑ์„ ํ†ตํ•ด ๋ฐœ์ƒํ•˜๋Š” ์‚ฌ์šฉ์ž ํƒ€์ดํ•‘ ๋ฐ์ดํ„ฐ๋Š” `channel.sink.add()`๋ฅผ ํ†ตํ•ด WebSocket ํ”„๋ ˆ์ž„์œผ๋กœ ์ฉ๋‹ˆ๋‹ค. + * ํ„ฐ๋ฏธ๋„ ๋ฆฌ์‚ฌ์ด์ฆˆ(`Terminal.onResize`) ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด `{"action": "resize", "cols": cols, "rows": rows}` ํ˜•ํƒœ์˜ JSON ์ œ์–ด ํ”„๋ ˆ์ž„์„ ์†Œ์ผ“์œผ๋กœ ์ „์†กํ•˜์—ฌ ๋ฐฑ์—”๋“œ PTY FFI ๋‹จ์—์„œ `ioctl(TIOCSWINSZ)`์ด ๊ธฐ๋™๋˜๋„๋ก ์—ฐ๋™ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 3. ์•ˆ์ •์„ฑ ๋ฐ ๋ณด์•ˆ ์š”๊ตฌ์‚ฌํ•ญ (DoD Requirements) + +* **์ž์› ๋ˆ„์ˆ˜ ๋ฐฉ์ง€ (Anti-Leak)**: ์›น ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ฐ‘์ž๊ธฐ ์ข…๋ฃŒ๋˜๊ฑฐ๋‚˜ ๋„คํŠธ์›Œํฌ ๋Š๊น€ ํ˜„์ƒ์ด ๋ฐœ์ƒํ•  ๋•Œ, ๋ฐฑ์—”๋“œ ๋ฐ๋ชฌ์ด ํ•˜ํŠธ๋น„ํŠธ (Ping-Pong) ๋˜๋Š” ์†Œ์ผ“ ์—๋Ÿฌ ์ด๋ฒคํŠธ๋ฅผ ์ฆ‰๊ฐ ๊ฐ์ง€ํ•ด `waitpid()`๋ฅผ ํ˜ธ์ถœํ•จ์œผ๋กœ์จ ์ข€๋น„ defunct ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹œ์Šคํ…œ์— ์ž”์กดํ•˜์ง€ ์•Š๋„๋ก ํ™•์‹คํžˆ ๋ณด์ฆํ•ฉ๋‹ˆ๋‹ค. +* **๋น„๋ธ”๋กœํ‚น ๋ณด์žฅ (Non-blocking)**: ๋ฐ๋ชฌ ์„œ๋ฒ„ ๋‹จ์—์„œ๋„ `fcntl` O_NONBLOCK ๋ฐ non-blocking read loop๊ฐ€ ๋™์ผํ•˜๊ฒŒ ๊ฐ€๋™๋˜์–ด ๋‹ค์ค‘ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ ‘์†ํ•˜๋”๋ผ๋„ ๋ฐฑ์—”๋“œ ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ์ •์ง€๋˜์ง€ ์•Š๋„๋ก ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค. +* **์ ‘์† ๊ถŒํ•œ ์ œ์–ด**: ๋ธŒ๋ฆฟ์ง€ ์„œ๋ฒ„๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กœ์ปฌํ˜ธ์ŠคํŠธ(`127.0.0.1`) ๋ฐ”์ธ๋”ฉ์œผ๋กœ ๊ธฐ๋™ํ•˜์—ฌ ์™ธ๋ถ€ ์›๊ฒฉ์ง€๋กœ๋ถ€ํ„ฐ์˜ ์•…์„ฑ ํ„ฐ๋ฏธ๋„ ํƒˆ์ทจ ๊ณต๊ฒฉ์„ ์›์ฒœ ๋ด‰์‡„ํ•ฉ๋‹ˆ๋‹ค.