Skip to content

feat(reasonix): embed reasonix web chat UI as an instance kind (MVP) - #50

Merged
linletian merged 8 commits into
developfrom
feature/reasonix-native-ui
Aug 11, 2026
Merged

linletian merged 8 commits into
developfrom
feature/reasonix-native-ui

Conversation

@linletian

Copy link
Copy Markdown
Owner

概要

新增第二种实例类型 :勾选创建后不启动 PTY 终端,而是在 worktree 内运行 reasonix serve 子进程,并将其官方 web 聊天界面经同源反向代理 /rx/<id>/ 以 iframe 嵌入实例标签页。

组件

层 内容
模型 ManagedInstance.Kind(tty/reasonix,omitempty,旧 state 无需迁移)
Driver internal/instance/reasonix:隔离 REASONIX_HOME、~/.reasonix config/.env symlink 继承、内核分配端口(--port-file 权威)、固定 session 文件、跨平台探活(kill(pid,0)+TCP,无 /proc)
Manager kind 分支 Start/Stop/Restart/Delete;Reconcile 重启后接管存活 serve(防孤儿);Delete 前兜底 Stop
反代 /rx/<id>/:reasonix_token Cookie 注入、RawQuery 剥离、Accept-Encoding identity、SSE 透传、服务端改写根相对属性 + MutationObserver 兜底动态节点
前端 iframe 渲染分支(dataset 守卫防 2s 轮询重载)、创建对话框 checkbox、tab rx 徽标

安全说明

同源 iframe 权限提升权衡已论证于 docs/plans/reasonix-native-ui/FEASIBILITY.md §9.5(远程模式风险最高,独立源方案列为 #44)。

评审与测试

文档

docs/API.md §5.10、docs/ARCHITECTURE.md §3.2/§4.2、docs/PRD.md §7、docs/plans/reasonix-native-ui/(FEASIBILITY/README/DEFERRED)

整合 reasonix web UI 嵌入调研(方案 A 反代+iframe 推荐)+ 三项实测结论:
- session lease 按 session 文件路径互斥,多 worktree 各起一个 serve 可并行
- 端口以 --port-file 为权威;serve 无自动递增、web 自动 +1
- /events 每 15s 自带 : ping 保活;Cookie: reasonix_token 注入实测 200 无重定向
- 布局问题:侧栏 220px 固定,需反代 + 轻量注入解锁折叠(§9)
Add a second instance type: kind=reasonix runs a per-worktree
'reasonix serve' subprocess and renders its web chat UI in an iframe
via a same-origin reverse proxy at /rx/<id>/.

- store: ManagedInstance.Kind (tty|reasonix, omitempty, no migration)
- internal/instance/reasonix: driver with isolated REASONIX_HOME,
  ~/.reasonix config/.env symlinked in, kernel-assigned port via
  --port-file, fixed session file, cross-platform liveness
  (kill(pid,0) + TCP probe, no /proc), SIGTERM->SIGKILL stop
- manager: kind branch for Start/Stop/Restart/Delete; Reconcile
  re-attaches live serve processes on startup (R-02); Delete stops
  before wiping state dir (N-01)
- internal/app/reasonix_proxy: /rx/<id>/ reverse proxy with
  reasonix_token cookie injection, RawQuery stripped, Accept-Encoding
  forced to identity, SSE streaming, HTML URL-prefix rewrite for
  fetch/EventSource/XHR
- frontend: iframe rendering branch, create-dialog checkbox, rx badge
- docs: API §5.10, ARCHITECTURE §3.2/4.2, PRD §7, plans index +
  FEASIBILITY §9.5 security tradeoff, DEFERRED notes

Two rounds of code review applied (R-01..R-08, N-01..N-06, T-01..T-03);
follow-ups tracked in issues #43..#49. Pre-existing app test failures
(TestHandleInstanceUpdate/Reorder, real-user state dir) tracked in #43.
…r-side

Runtime issues found when exercising the MVP:

- iframe reloaded every 2s poll tick: renderWorkspace() -> ensureWebFrame()
  assigned frame.src unconditionally; guard it with a dataset.instance
  check so the embedded page only navigates when the instance changes
  (trailing slash added so in-page relative URLs resolve under /rx/<id>/).
- <img src="/assets/logo-wordmark.svg"> 404'd: the URL-prefix shim only
  covered fetch/EventSource/XHR. Rewrite root-relative src/href/action/
  poster attributes server-side in ModifyResponse (rewriteRootAttrs) so
  the browser never issues a wrong first request, and keep a
  MutationObserver for dynamically inserted nodes.

rewriteRootAttrs (two-level tag->attr regex):
- all attributes in one tag rewritten (multiple-attrs case)
- case-insensitive (?i), unquoted values, idempotent (already-prefixed and
  protocol-relative //host URLs skipped), data-src/xlink:href and JS
  property assignments (el.src=) not matched
- documented regex tradeoffs (value containing '>' truncates; no second
  pass for initial page; CSS url()/srcset/SVG <use> uncovered)

Tests: TestRewriteRootAttrs grown to 13 cases incl. the previously
uncovered risk path (two attrs in one tag), verified via control
experiment; proxy HTML-injection test now asserts real rewritten output
instead of grepping for script source.
GitHub macOS runners have broken/unresponsive DNS, and
HTTPServer.server_bind() calls socket.getfqdn() for a reverse lookup
that hangs indefinitely. The fake reasonix serve scripts (python3
http.server) never wrote --port-file, so every reasonix test timed
out with 'serve did not become ready within 15s' on macOS only.
Patch socket.getfqdn to skip the DNS lookup in the test scripts.
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.

1 participant