feat(sessions): add full-access startup and fix external rendering - #165
Conversation
리뷰 요약레포를 체크아웃해서 서버 권위 검증(고정 인자만 사용해 브라우저에서 argv 주입 불가), 인증 미들웨어 뒤에 있는 라우트, 10개 로케일 키 전부 존재, 문서/테스트 커버리지 모두 좋습니다. 다만 LaTeX 정규화에 실제로 재현되는 버그 2건이 있어 이대로 머지하면 채팅 렌더링이 깨집니다. 🔴 1. 수식 모드가 코드블록을 먹어버림 (코드 내용 훼손)
sed BRE( 제안: math 스캔 중에도 펜스를 추적하거나, 최소한 빈 줄 / 펜스 시작을 만나면 math 후보를 포기하도록 (CommonMark 인라인도 빈 줄을 넘지 못합니다). 🔴 2. 마크다운 이스케이프
|
| 연속 백슬래시 | 소요 시간 |
|---|---|
| 2,000 | 3.8 ms |
| 20,000 | 143 ms |
| 100,000 | 2.9 s (렌더 스레드 프리즈) |
제안: text[cursor] === '\\' 체크를 앞에 두거나, 루프에서 escaped 플래그를 들고 다니면 문자당 O(1).
🟡 4. codex 추론 폴링 비용 & 결과 흔들림
server/modules/providers/services/external-cli-sessions/codex-fork-inference.ts:307— 조기반환이!forks.length→!initial.length로 완화되고shouldInferSharedCodexDisplay에서providerSessionId조건도 빠졌습니다. 그 결과 평범한 codex 세션마다 폴링 때마다capture-pane이 추가로 실행됩니다.- 같은 파일
:351— anchor 없는 초기 세션은forkBindingsByTarget에 캐시하지 않습니다. 앵커가 스크롤로 밀리면 바인딩이 붙었다 떨어졌다 할 수 있고, 캐시가 없으니target.previous폴백도 잡히지 않습니다.
🟡 5. full-access 세션의 사후 표식이 없음
경고가 생성 시점에만 노출되고, 이후 세션 목록에서는 일반 세션과 구분되지 않습니다.
제안: server/modules/providers/services/external-cli-sessions/inference-and-spawn.ts:410 에서 @chatmux_cli_kind 를 태그하듯 @chatmux_full_access 도 함께 달고 UI에 배지 노출. 공유 인스턴스를 위한 배포 단위 킬스위치(예: CHATMUX_DISABLE_FULL_ACCESS)도 있으면 좋겠습니다.
사소한 것들
src/components/chat/view/subcomponents/Markdown.tsx:188—content만useMemo가 없습니다(바로 아래remarkPlugins/rehypePlugins는 memo). 스트리밍 중 매 렌더마다 전체 문자열을 다시 스캔합니다.supportsFullAccessSpawn을 capabilities API에 추가했지만 클라이언트는shared/external-cli-spawn상수를 직접 import 해서 씁니다 — 타입만 늘고 실사용처가 없습니다.ExternalSpawnCli가shared/external-cli-spawn.ts와contracts-and-resume.ts:77(ExternalLocalCliKind) 두 곳에 각각 정의돼 있습니다.ExternalLocalCliKind만 늘어나는 방향은 타입 에러 없이 통과해서, 새 CLI가 라우트에서 조용히 거부될 수 있습니다.buildExternalCliTmuxSpawnArgs(executable, tmuxName, cwd, undefined, cliArgs)— 자리 채우기용undefined. 옵션 객체가 더 나아 보입니다.
새로 추가된 chatFormatting.test.ts 는 해피패스와 "코드 안에 있는 경우"를 덮지만, 위 1·2번 케이스는 커버되지 않습니다.
|
리뷰 지적을 4bbc078에서 반영했습니다.
검증: npm run verify 통과(서버 1,861, 실제 tmux/PTY 50, 클라이언트 741 테스트, 타입 검사, ESLint, Rust 검사, identity 검사, 프로덕션 빌드 포함). |
후속 리뷰 (
|
| 항목 | 결과 |
|---|---|
| 🔴 수식 모드가 코드블록을 먹음 | 빈 줄 / 펜스 / 백틱에서 math 포기 → 해결 |
🔴 \[1\] 을 display 수식으로 오인 |
prefixOnlyWhitespace + strongMathSyntax 게이트 → 해결 |
| 🟡 백슬래시 O(n²) | precedingBackslashes 카운터로 전환. 10만 개 기준 2.9 s → 4.9 ms |
| 🟡 codex 추론 폴링 비용 / 결과 흔들림 | attemptableTargetKeys 게이트 + anchor 없는 바인딩도 캐시 → 해결 |
세 건 모두 테스트도 함께 추가된 점 좋습니다.
🔴 다만 이번 커밋에서 새 회귀가 하나 생겼습니다
src/components/chat/utils/chatFormatting.ts:34
isMathBoundaryLine 이 4칸/탭 들여쓰기 줄까지 경계로 취급해서, 본문이 들여쓰인 display 수식이 변환되지 않습니다.
IN : \[
\begin{aligned}
a &= b \\
c &= d
\end{aligned}
\]
OUT: (그대로 — $$ 로 변환되지 않음)
- 2칸 들여쓰기는 통과하지만 4칸부터 실패합니다.
- 탭 들여쓴 본문도 동일하게 실패합니다.
aligned/bmatrix블록 본문을 들여쓰는 건 흔한 출력 형태라, 이 PR이 고치려던 바로 그 케이스가 다시 깨집니다.
제안 (한 줄): math 내부 bail 조건에서 들여쓰기 검사 두 줄(line.startsWith(' '), line.startsWith('\t'))만 제거.
CommonMark에서 들여쓰기 코드블록은 앞에 빈 줄이 있어야 시작되고 빈 줄은 이미 bail 조건이므로 안전합니다. 바깥 스캐너의 들여쓰기 검사(:170)는 그대로 두면 됩니다.
로컬에서 해당 두 줄만 제거해 확인한 결과:
- 들여쓴 display 수식(4칸 / 탭 /
aligned) → 전부 정상 변환 - 펜스 크로싱, 인용 대괄호
\[1\], 펜스 내부, 인라인 코드, 빈 줄 뒤 들여쓰기 코드블록 → 전부 그대로 보호됨 (회귀 없음)
🟡 남은 것들
- 인라인 경로에는 게이트가 없습니다 —
:104의shouldNormalize = math.replacement === '$' || ...때문에 인라인\(...\)는 무조건 변환됩니다. 예:Run sed 's/\(foo\)/bar/' to rename.→Run sed 's/$foo$/bar/' to rename.로 백슬래시가 사라집니다. 코드블록 훼손은 아니고 해당 문장만 잘못 렌더되는 수준이라 우선순위는 낮습니다. =를 math 신호로 보는 것이 다소 셉니다 —:126.Usage: cmd \[--flag=value\]→$$--flag=value$$. 판정에서=를 빼도\[E = mc^2\]는^덕분에 그대로 통과합니다.- anchor 없는 바인딩은 내용 재검증을 하지 않습니다 — pid 집합이 동일하면 계속 재사용되므로, 같은 TUI에서
/new를 하면 이전 스레드가 계속 표시될 수 있습니다. TTL이나 주기적 재확인을 고려해볼 만합니다. - 성능 테스트가 벽시계
< 2_000 ms단언이라 CI 부하 시 약간 flaky할 수 있습니다. 동일 길이의 비백슬래시 문자열 대비 비율로 비교하면 더 안정적입니다.
미반영 (지난 nit, 모두 선택사항)
Markdown.tsx:188 의 content useMemo, @chatmux_full_access tmux 태그 + UI 배지, 배포 단위 킬스위치, ExternalSpawnCli 타입 이중 정의.
|
후속 리뷰를 c0d1cf2에서 반영했습니다.
검증: npm run verify 통과(서버 1,861, 실제 tmux/PTY 50, 클라이언트 743 테스트, 타입 검사, ESLint, Rust 검사, identity 검사, 프로덕션 빌드 포함). |
3차 리뷰 (
|
| 항목 | 처리 |
|---|---|
| 🔴 들여쓴 display 수식이 변환되지 않음 | isMathBoundaryLine 에서 들여쓰기 검사 두 줄 제거 → 4칸 / 탭 / aligned / bmatrix 전부 복구 |
🟡 = 가 math 신호로 너무 셈 |
strong 판정에서 = 제거 → \[--flag=value\] 는 RAW 유지, \[E = mc^2\] 는 ^ 로 통과 |
| 🟡 anchorless 바인딩 재검증 없음 | validatedAtMs + 60초 TTL 도입, 만료 시 fail closed 로 옛 스레드를 계속 보여주지 않음 |
| 🟡 성능 테스트 flaky | 평문 대비 비율 + 최소 250 ms 바닥값으로 변경 |
특히 anchorless TTL 을 "만료 시 이전 값 유지"가 아니라 "fail closed"로 간 선택이 좋습니다.
🟡 남은 것 하나 — 인라인 게이트가 과하게 조여졌습니다
shouldNormalize 에서 math.replacement === '$' || 단축을 제거하면서, 인라인도 display 와 동일한 기준(\cmd / ^ / _ 또는 줄 단독 + 줄 끝)을 요구하게 됐습니다. 그 결과 LLM 출력에서 가장 흔한 형태의 인라인 수식이 변환되지 않습니다.
원소가 \(n\)개 있습니다. → RAW (\(n\) 이 그대로 노출)
value \(x+1\) done → RAW
when \(x = y\) holds → RAW
points \(a, b\) given → RAW
값은 \(x \times y\) 입니다. → 변환됨 (\times 덕분)
\(x\) → 변환됨 (줄 단독)
즉 LaTeX 명령이나 ^ / _ 가 들어간 인라인만 살아남습니다. 제가 지적했던 sed 오탐은 상대적으로 드문 케이스였는데, 그걸 막으려다 흔한 케이스를 놓친 과교정으로 보입니다.
권고 A (간단): 인라인은 무조건 변환으로 되돌리기. \( 는 Markdown 에서 이스케이프 의미가 사실상 없어서 오탐 위험이 낮습니다. sed 오탐은 해당 문장만 이상해지는 수준이고 코드블록 훼손은 이미 별도로 막혀 있습니다.
권고 B (로컬 검증함): 무조건 변환 + \( 바로 앞이나 \) 바로 뒤가 / 인 경우만 건너뛰기.
로컬에서 이 가드만 붙여 확인한 결과:
- 위 인라인 4개 전부 복구
Run sed 's/\(foo\)/bar/' to rename.→ 계속 RAW- 펜스 크로싱 / 인용 대괄호 / 펜스 내부 / 인라인 코드 / 들여쓴 display 수식 → 전부 기존과 동일하게 보호됨
다만 grep '\(a\|b\)' 처럼 슬래시가 인접하지 않은 BRE 는 걸러내지 못합니다. 휴리스틱이라 완벽하진 않아서, 단순함을 원하시면 A 를 권합니다.
사소한 것
TTL 이 만료된 뒤 재선택에 실패하면 forkBindingsByTarget 에 만료된 엔트리가 그대로 남아 매 폴링마다 재검증만 반복합니다. 동작상 무해하지만 실패 시 delete 해주면 더 깔끔합니다.
전반적으로 잘 수렴했습니다. 인라인 게이트만 정리되면 머지해도 될 것 같습니다. 👍
|
3차 리뷰의 남은 항목을
검증: |
✅ LGTM
최종 HEAD에서 전체 재확인한 결과입니다. 복구된 인라인 수식 계속 보호되는 것 display 수식: 4칸 들여쓰기, 탭, 리뷰 라운드 전체에서 제기한 항목(코드블록 훼손, 이스케이프 대괄호 오인, O(n²), codex 폴링 비용/흔들림, 들여쓰기 회귀, 인라인 과교정, 캐시 정리) 전부 해결됐습니다. 머지해도 좋겠습니다. 알려진 잔여 (머지 블로커 아님)
(검증 방법: PR HEAD 를 체크아웃해 |
## Summary Prepares the v1.12.0 minor release from current main. - bumps package.json to 1.12.0 - aligns both root package-lock.json version fields - adds the exact 1.12.0 rollback-compatibility declaration ## Release contents since v1.11.1 - #165 - feat(sessions): add full-access startup and fix external rendering This is a **minor** bump, not a patch: #165 adds a user-facing capability (the per-session Full access option for newly launched local CLIs) and extends the API surface with `fullAccess` on the external spawn route and `supportsFullAccessSpawn` on the provider capabilities response. It follows the existing convention in this repository, where a release containing a `feat` change takes a minor bump (1.11.0 after #158, 1.10.0 after #157, 1.9.0 after #132). > [!IMPORTANT] > #165 must merge before this PR. The branch is cut from main, so merging this first would ship a 1.12.0 that does not contain the feature it is named for. ## Schema and rollback The database migration registry is unchanged from v1.11.1; #165 adds no migration. Schema generation remains 20, and rollback compatibility carries forward every version declared by 1.11.1 plus v1.11.1 itself. The declaration is append-only (16 insertions, 0 deletions), so no published entry is modified. ## Verification - `npm run release:check-metadata` → passed: `Release metadata check passed for 1.12.0 (schema generation 20; 9 rollback-compatible version(s) declared)` Run without a canonical declaration or canonical migration registry, so the two immutability warnings are expected in a local run; CI supplies those from the predecessor tag. `npm run verify` was **not** run for this PR — it only changes version metadata, and #165 already reports a full verify pass on its own branch. Please let CI confirm before merging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: YoonwooHa <he0653@g.skku.edu> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Summary - distinguish BRE-only escapes such as `grep '\(a\|b\)'` from ordinary inline math while allowing units such as `speed \(v\)/s` - memoize Markdown normalization and consolidate the external spawn CLI type onto the shared contract - tag full-access tmux sessions, propagate the tag through discovery, and retain a visible sidebar warning badge - add the server-authoritative `CHATMUX_DISABLE_FULL_ACCESS=1` kill switch to capability discovery and spawn enforcement - document the deployment control and persistent warning behavior in the provider guide and localized READMEs Follow-up to #165, based on the `1.12.0` main from #166. ## Verification - `npm run verify` on `1.12.0` - Rust: 29 passed - server: 1,863 passed - real tmux/PTY: 50 passed - client: 746 passed - lint, identity check, and production builds passed --------- Co-authored-by: YoonwooHa <he0653@g.skku.edu>
Summary
\(...\)and\[...\]delimiters outside Markdown code so KaTeX renders them without dropping command prefixesGJC remains unchecked because its guarded tools already default to allow and it has no per-launch full-access switch. Cursor remains unchecked because no supported interactive full-access flag was verified.
Verification
env -u CHATMUX_AUTH npm run verifyopencode --auto,omp --approval-mode=yolo, andomo --approve --permission-preset full-access/healthsmoke passedKnown audit output