Skip to content

feat(sessions): add full-access startup and fix external rendering - #165

Merged
Yoonwoo-Ha merged 6 commits into
mainfrom
feat/full-access-new-session
Sep 21, 2026
Merged

Yoonwoo-Ha merged 6 commits into
mainfrom
feat/full-access-new-session

Conversation

@Yoonwoo-Ha

@Yoonwoo-Ha Yoonwoo-Ha commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • add an opt-in Full access checkbox for verified local interactive CLIs
  • map it server-side for Claude, Codex, OpenCode, OMP, and OMO; reject unsupported providers and invalid values
  • show a localized warning while Full access is selected and connect it to the checkbox for assistive technology
  • document the user-facing support matrix, per-session scope, risks, fixed provider arguments, and server authority contract
  • recover initial Codex/Agent-Add transcript linkage when a shared app-server owns the rollout, using exact cwd and unique pane evidence
  • normalize paired LaTeX \(...\) and \[...\] delimiters outside Markdown code so KaTeX renders them without dropping command prefixes
  • add focused route, capability, tmux argument, inference, mounted UI, and Markdown rendering coverage

GJC 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 verify
    • server: 1,859 passed
    • real tmux/PTY: 50 passed
    • client: 738 passed
    • typecheck, Rust checks, lint, identity check, and production build passed
  • focused mounted new-session tests: 12 passed
  • all sidebar locale JSON files parsed successfully
  • live isolated tmux startup probes verified opencode --auto, omp --approval-mode=yolo, and omo --approve --permission-preset full-access
  • local release bundle and /health smoke passed

Known audit output

  • npm audit reports one existing Moderate Hono advisory; the configured High gate passes.

@Yoonwoo-Ha Yoonwoo-Ha changed the title feat(sessions): add full-access native CLI startup feat(sessions): add full-access startup and fix external rendering Sep 20, 2026
@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

리뷰 요약

레포를 체크아웃해서 normalizeLatexMathDelimiters를 직접 실행해보며 확인했습니다.

서버 권위 검증(고정 인자만 사용해 브라우저에서 argv 주입 불가), 인증 미들웨어 뒤에 있는 라우트, 10개 로케일 키 전부 존재, 문서/테스트 커버리지 모두 좋습니다. 다만 LaTeX 정규화에 실제로 재현되는 버그 2건이 있어 이대로 머지하면 채팅 렌더링이 깨집니다.


🔴 1. 수식 모드가 코드블록을 먹어버림 (코드 내용 훼손)

src/components/chat/utils/chatFormatting.ts:67

\( 가 한 번 열리면 그 뒤로는 펜스/인라인코드 상태머신이 완전히 꺼집니다. 닫는 \) 를 코드블록 안에서 찾으면 코드블록 내용을 고쳐버립니다.

IN :  Group with \( in BRE.

      ```sh
      sed 's/\(foo\)/bar/' file
      ```

OUT:  Group with $ in BRE.

      ```sh
      sed 's/\(foo$/bar/' file     ← 코드가 망가짐
      ```

sed BRE(\(...\))는 코딩 에이전트 출력에 흔해서 현실적으로 잘 터집니다. 함수 docstring은 "outside Markdown code"를 보장한다고 하지만, math가 열린 뒤로는 그 보장이 사라집니다.

제안: math 스캔 중에도 펜스를 추적하거나, 최소한 빈 줄 / 펜스 시작을 만나면 math 후보를 포기하도록 (CommonMark 인라인도 빈 줄을 넘지 못합니다).


🔴 2. 마크다운 이스케이프 \[1\] 을 display 수식으로 오인

src/components/chat/utils/chatFormatting.ts:129

IN :  See \[1\] and \[2\].
OUT:  See $$1$$ and $$2$$.

대괄호 이스케이프(인용 번호, \[options\] 같은 usage 문구, 이스케이프된 링크 문법)가 전부 거대한 KaTeX display 블록이 됩니다.

제안: display \[ 는 줄 단독일 때만 허용하거나, 내용에 LaTeX 토큰(\cmd, ^, _, = 등)이 있을 때만 변환.


🟡 3. 백슬래시 연속 구간에서 O(n²)

src/components/chat/utils/chatFormatting.ts:128

backslashIsEscaped 를 startsWith 검사보다 먼저, 모든 문자 위치마다 호출합니다. 백슬래시가 이어지면 매 위치에서 뒤로 되짚습니다.

연속 백슬래시 소요 시간
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번 케이스는 커버되지 않습니다.

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

리뷰 지적을 4bbc078에서 반영했습니다.

  • 닫히지 않은 LaTeX 수식 후보가 빈 줄, fenced/indented code, inline code를 넘어가지 않도록 수정
  • 인용 표기 같은 Markdown 이스케이프(예: [1])는 보존하고, 독립된 줄 또는 명확한 LaTeX 문법만 display math로 변환
  • 백슬래시 parity를 단일 패스로 추적해 O(n²) 동작 제거(로컬 100,000자 회귀 테스트 약 9ms)
  • 미연결 Codex 세션은 기존 inference retry backoff를 따르고, anchor 없는 성공 매핑도 동일 프로세스 세대에서 캐시하며 anchor/세대 변경 시 재검증
  • 위 사례를 회귀 테스트로 추가

검증: npm run verify 통과(서버 1,861, 실제 tmux/PTY 50, 클라이언트 741 테스트, 타입 검사, ESLint, Rust 검사, identity 검사, 프로덕션 빌드 포함).

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

후속 리뷰 (4bbc078 fix: address PR review regressions)

새 HEAD를 체크아웃해서 다시 실행해봤습니다.

지난 지적은 모두 수정 확인 ✅

항목 결과
🔴 수식 모드가 코드블록을 먹음 빈 줄 / 펜스 / 백틱에서 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 타입 이중 정의.

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

후속 리뷰를 c0d1cf2에서 반영했습니다.

  • math 내부 경계에서 4칸/탭 들여쓰기 판정을 제거해 들여쓴 aligned·bmatrix display 수식을 정상 변환
  • 인라인 수식도 독립된 줄 또는 명확한 LaTeX 문법이 있을 때만 변환하여 sed BRE의 (...) 보존
  • 비독립 display 판정에서 = 단독 신호를 제거하여 [--flag=value] 보존
  • anchor 없는 Codex 표시 바인딩은 60초마다 재검증하고, 만료된 재검증이 실패하면 이전 스레드로 폴백하지 않도록 fail closed
  • 백슬래시 성능 테스트를 같은 길이의 일반 문자열 대비 비율 검사로 변경
  • 들여쓴 display, sed BRE, flag=value, 캐시 만료 회귀 테스트 추가

검증: npm run verify 통과(서버 1,861, 실제 tmux/PTY 50, 클라이언트 743 테스트, 타입 검사, ESLint, Rust 검사, identity 검사, 프로덕션 빌드 포함).

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

3차 리뷰 (c0d1cf2 fix: harden math parsing and codex cache refresh)

새 HEAD 받아서 다시 실행해봤습니다.

지난 지적 4건 전부 반영 확인 ✅

항목 처리
🔴 들여쓴 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 해주면 더 깔끔합니다.


전반적으로 잘 수렴했습니다. 인라인 게이트만 정리되면 머지해도 될 것 같습니다. 👍

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

3차 리뷰의 남은 항목을 8e73c2e에서 반영했습니다.

  • 일반적인 인라인 수식 \(n\), \(x+1\), \(x = y\), \(a, b\)를 다시 $...$로 정규화
  • 여는 \( 바로 앞이나 닫는 \) 바로 뒤가 /인 경우는 BRE로 보고 원문 보존
  • sed BRE 및 양쪽 슬래시 인접 조건을 회귀 테스트로 고정
  • 만료된 anchorless Codex 바인딩은 재검증 전에 캐시에서 제거하여 실패 후 매 폴링 재시도 방지

검증: npm run verify 통과(서버 1,861, 실제 tmux/PTY 50, 클라이언트 743, Rust 29 테스트, 타입 검사, ESLint, identity 검사, 프로덕션 빌드 포함).

@Yoonwoo-Ha

Copy link
Copy Markdown
Collaborator Author

✅ LGTM

8e73c2e 확인했습니다. 권고 B(슬래시 인접 가드)를 양방향(여는 \( 앞, 닫는 \) 뒤)으로 넣어서 제안보다 더 촘촘하게 들어갔네요. 캐시 만료 엔트리를 I/O 이전에 delete 하는 것도 반영됐습니다.

최종 HEAD에서 전체 재확인한 결과입니다.

복구된 인라인 수식

원소가 \(n\)개 있습니다.   → $n$
value \(x+1\) done         → $x+1$
when \(x = y\) holds       → $x = y$
points \(a, b\) given      → $a, b$
값은 \(x \times y\) 입니다. → $x \times y$

계속 보호되는 것

sed 's/\(foo\)/bar/'                 → RAW
Pattern \(foo\)/bar                  → RAW
See \[1\] and \[2\].                 → RAW
Usage: cmd \[--flag=value\]          → RAW
펜스 내부 / 인라인 코드 / 펜스 크로싱 → RAW

display 수식: 4칸 들여쓰기, 탭, \begin{aligned}, \begin{bmatrix}, 한글 \text{} 모두 정상 변환
성능: 백슬래시 10만 개 6.6 ms (초기 2.9 s → 선형 복구)

리뷰 라운드 전체에서 제기한 항목(코드블록 훼손, 이스케이프 대괄호 오인, O(n²), codex 폴링 비용/흔들림, 들여쓰기 회귀, 인라인 과교정, 캐시 정리) 전부 해결됐습니다. 머지해도 좋겠습니다.

알려진 잔여 (머지 블로커 아님)

  • grep '\(a\|b\)' 처럼 슬래시가 인접하지 않은 BRE 는 여전히 변환됩니다. 가드가 휴리스틱이라 생기는 한계이고, 제안 시점에 공유드린 내용입니다.
  • speed \(v\)/s 처럼 뒤에 / 가 붙은 진짜 인라인 수식은 RAW 로 남습니다. 가드의 대가인데 빈도는 아주 낮습니다.
  • 이전 라운드의 optional nit 4건은 미반영입니다: Markdown.tsx 의 content useMemo, @chatmux_full_access tmux 태그 + UI 배지(생성 이후에는 full-access 세션을 식별할 방법이 없음), 배포 단위 킬스위치, ExternalSpawnCli 타입 이중 정의. 별도 이슈로 따라가도 충분합니다.

(검증 방법: PR HEAD 를 체크아웃해 normalizeLatexMathDelimiters 를 직접 실행. 레포 전체 테스트 스위트는 돌리지 않았고, PR 본문의 npm run verify 결과에 의존합니다.)

@Yoonwoo-Ha
Yoonwoo-Ha merged commit 6430715 into main Sep 21, 2026
5 checks passed
@Yoonwoo-Ha
Yoonwoo-Ha deleted the feat/full-access-new-session branch September 21, 2026 04:09
Yoonwoo-Ha added a commit that referenced this pull request Sep 21, 2026
## 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>
Yoonwoo-Ha added a commit that referenced this pull request Sep 21, 2026
## 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants