Skip to content

Fuse rmux terminal gateway into lucarned#36

Open
qinsehm1128 wants to merge 12 commits into
tuchg:mainfrom
qinsehm1128:feat/rmux-terminal-monitor
Open

Fuse rmux terminal gateway into lucarned#36
qinsehm1128 wants to merge 12 commits into
tuchg:mainfrom
qinsehm1128:feat/rmux-terminal-monitor

Conversation

@qinsehm1128

@qinsehm1128 qinsehm1128 commented Jun 1, 2026

Copy link
Copy Markdown

Summary

  • Fuse the rmux terminal monitor/gateway path into the default lucarned runtime and TUI entrypoint instead of requiring a separate feature-gated launch path.
  • Add typed remote configuration handling, provider field merging, loopback/control-port validation, strong token validation, and unified rmux binary resolution.
  • Harden the terminal gateway boundary: public gateway router excludes remote control routes, readonly sessions reject write frames, tickets are scoped/single-use/rate-capped, and resync handles have_rev correctly.
  • Add repeatable remote Quick Tunnel E2E harness/docs and cover the fusion boundary with unit, integration, and default-build tests.

Testing

  • cargo +nightly fmt --check
  • git diff --check HEAD
  • bash -n scripts/remote-quick-tunnel-e2e.sh && scripts/remote-quick-tunnel-e2e.sh
  • cargo +nightly clippy -Zbuild-dir-new-layout --workspace --all-targets --all-features --exclude agent-sessions --no-deps -- -D warnings
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarne --test live_unit codex_live_preflight_accepts_minimal_turn -- --exact
  • cargo +nightly test -Zbuild-dir-new-layout --workspace --all-features --exclude agent-sessions
  • Real Cloudflare Quick Tunnel E2E completed before PR submission; the checked-in harness remains env-gated with cleanup behavior for repeatable release validation.

Notes

  • Quick Tunnel is documented as a development/testing path; named Cloudflare tunnels remain the recommended production path.
  • The branch is merged with upstream main at 670e403 / release v0.4.3, with lockfile versions synchronized after merge.

终端监控子系统(rmux feature 门控,默认构建不拉入):
- lucarne-term: rmux-free 终端值类型 + 自写 snapshot differ + 会话注册表
- lucarne-rmux: rmux-sdk adapter + monitor(监控系统 daemon,Adopted/Managed)
- lucarne-termgw: axum 网关(ws 镜像 + /api 控制面 + 静态资源)
- lucarne-termctl: 薄 CLI(ls/new/attach/detach/kill + go-public)
- lucarne-web: web 双模客户端(终端镜像 + chat)

P5 远程可达(可插拔隧道适配器):
- lucarne-remote: RemoteAccessProvider trait + RemoteRegistry + Cloudflared 后端;
  FRP/relay/其它穿透留 trait seam + 通用 providers.<id> 配置占位
- lucarned: remote cargo feature 接线 + 通用 provider_fields 配置 + 控制面(127.0.0.1:7801)/网关(127.0.0.1:7800)分端口隔离
- 公网鉴权(落 gateway/web 层):access token(256-bit/常量时间) + 单次短 TTL ws ticket +
  AccessScope 只读档 + default-deny + loopback-only + 失败限流
- 一键开启 CLI:term go-public(选后端→提示→起隧道→public URL + QR + access key)
- 安全加固(经 codex 多维 review 三轮闭环):readonly 越权、/chat 绕过鉴权/限流、
  cloudflared stderr drain/health/reaper、锁跨 await、daemon idle-exit、provider 边界回归等

ADR: docs/decisions/2026-05-30-{rmux-terminal-monitor-subsystem,remote-access-tunnel-adapter}.md
默认 lucarned 构建零警告且 cargo tree 不含 remote crates;cargo +nightly test 全绿。
冷启动一键化(懒启动 gateway):
- 控制面 127.0.0.1:7801 在 --features remote 下常驻(boot 即可用,返回 token/running:false),不依赖 rmux
- gateway + monitor + tunnel 改懒启动:仅首次 control.start(term go-public 或 autostart)才连 rmux/绑 gateway/起隧道,once-guard 防双绑
- remote.enabled→autostart:true 则 boot 自动起隧道(保持原行为),false 则控制面就绪、隧道空闲等 term go-public
- 无 rmux 环境:go-public 返回明确错误而非 daemon 崩溃;feature-build 因控制面常驻而保活
- 全部既有安全闭环保留(控制面独立端口/loopback/default-deny/readonly scope/permit先于ticket/forwarded-identity)

其它:
- fix(termctl): 修过期注释(daemon 现经 RemoteStartParams 使用 CLI 字段,G3)
- chore(clippy): lucarne-term derive + lucarne-agentbind sort_by_key 清零
- test: 加 default_config_parses_including_remote_providers 回归守护(证伪 review 误报的 null 解析)

默认 lucarned 零警告且 cargo tree 不含 remote crates;cargo +nightly test 全绿。
经 4 路并行子代理对照原项目(AGENTS.md/既有 crate 约定)审查后修复:

provider 边界(AGENTS.md):
- lucarne-agentbind 彻底接入 agent-sessions provider descriptor 契约:
  删除 Claude 专属硬编码(~/.claude/projects 布局、kind:"claude" 字面量、
  手解 jsonl schema),改由 discovery/parse 契约驱动,kind=provider.id();
  bounded line-window 读取。消除 public 层 provider 细节泄漏。

日志/任务生命周期:
- tracing target 对齐:lucarne-termgw/rmux/web 改用 target:"lucarne_xxx",
  去掉消息连字符前缀(恢复按 crate 分级过滤)
- detached 任务可收口:RmuxMonitor source loops + WebChat 事件泵改 Drop-abort
  (持 JoinHandle/AbortHandle),对齐 AdapterSupervisorHandle 模式

测试:
- lucarned/src/remote.rs 补 17 内联测试(默认拒绝/状态机/错误映射/G3 合并)
- lucarne-term/rmux 新增 contract_boundary 集成测试(守护 rmux_sdk 隔离不变量)
- lucarne-web 测试 3→15(出站帧映射/FrameRate/open-ack 超时)

结构/元数据/文档:
- lucarne-web 单文件拆分为 state/router/session 三模块(对齐 channel 惯例)
- 8 个新 crate 补 Cargo description;ADR/注释 rmux→remote feature 名修正
- axum 上提 [workspace.dependencies];CLI bin 改名 lucarne-term→term(消除与库 crate 同名)
- ADR 修正 web 为 ws 桥(非 Channel trait)措辞;README 双语同步新子系统+Roadmap
- clippy 清零:lucarne-term/agentbind/archive 的 sort_by_key

默认 lucarned 零警告且 cargo tree 不含 remote/axum;cargo +nightly test 全绿。
实机测试发现:lucarned.yaml 的 `remote.providers.<id>.{token,public_url}: null`
经 serde 反序列化成字符串 "null"(非空),漏过空值过滤后传给 provider;cloudflared
据此误判为 named tunnel,再把 public_url="null" 当 URL 解析 → "invalid public_url
for named tunnel: relative URL without a base",隧道起不来。examples/lucarned.yaml
默认就写 null,照抄者必中招。

- RemoteFileConfig.providers 内层值 String → Option<String>:YAML null → None → 视为 absent
- resolve_provider_fields 跳过 None/空值(quick 模式回归)
- 更新 remote_file_config_parses_full_section 断言;default_config_parses_… 增回归断言
  (null 字段 resolve 后必须 absent)

经真实 cloudflared quick 隧道端到端验证:公网可达 + 鉴权全闭环(无/错 token→401,
带 token→换 ticket→200 列出 3 会话,手机实连成功);lucarned --features remote 91 测试全绿。
新增唯一交互入口 `lucarned tui`(feature-gated 全屏 ratatui TUI,opencode 式方向键导航),彻底移除独立 `term` 二进制,其可复用逻辑迁入 lucarned,不重写。

- 三面板:Sessions(rmux 会话 列表/attach 弹出/detach/kill/archive)、Go Public(经既有 loopback /api/remote/* 启停隧道 + 高对比 QR 模态 + 凭证)、Config(lucarne_remote 描述符驱动的 provider 字段表单 → 带备份写回 lucarned.yaml)
- feature `tui`(implies remote);ratatui 0.30 + crossterm 0.29 仅在该 feature 下,默认构建零 ratatui/crossterm/rmux(tests/default_build_purity.rs 守护)
- 终端恢复:raw mode/alternate screen + 进程级 panic hook;attach 用 挂起→exec rmux→重入 交接,RAII 守卫防 raw mode 泄漏
- fix: TUI 在 #[tokio::main] 内经 reqwest::blocking drop 嵌套运行时 panic → 改在无运行时上下文的独立 OS 线程执行(含回归测试)
- 安全:lucarned.yaml + 备份写入 0o600;access token 仅显示"已设置"+ 经 QR 分享;控制面不可达时给出可操作提示(引导启动守护进程)
- 零新建 daemon 控制 IPC;不在 common 层硬编码 provider(守 AGENTS.md 边界)
- docs: README/README.cn/commands.md 改用 lucarned tui;新增 ADR 2026-06-01-lucarned-tui-frontend

锁定决策:经用户授权放宽合并范围 tuchg#5(允许薄 TUI),tuchg#6 薄包装/零新 IPC 精神保留。
介绍从独立 term CLI 到唯一 `lucarned tui` 入口的改动,以及三面板用法:
- 构建/启动(`--features tui`,默认构建不含 ratatui)
- 各面板键位(Sessions / Go Public / Config)
- Go Public 需 lucarned 守护进程在跑(守护进程拥有隧道生命周期,控制面 127.0.0.1:7801)+ 端到端两终端示例
- 架构边界(零新建 IPC、非实时镜像、provider 边界、feature 隔离)
- `term` → `lucarned tui` 迁移对照表
README.md / docs/commands.md 各加一行指向 docs/tui.md。
PART 1 — TUI 内"配置 + 起隧道",免先编辑 yaml:
- ConfigPanel::start_params() 返回当前 provider + 非空字段(纯函数)
- GoPublicPanel::start_with(provider, fields) 用给定参数组装 plan;保留零参 start() 作守护进程默认
- App 桥接:Go Public 面板按 s 时读 config.start_params() → go_public.start_with(...)
- 发送前经 provider 描述符 validate_config 校验,失败内联提示且不发送
- Go Public 正文显示起源行(start uses Config: provider=… / daemon default)
- docs/tui.md 说明 s 使用 Config 面板实时字段

PART 2 — 6 项评审 Low 清理:
- COR-003: 先绘制首帧再做阻塞 refresh;事件循环改 event::poll(1s) 就绪才 read(无忙等)
- COR-004: QR 回退模态宽度用 chars().count() 而非字节 len()
- COR-005: 区分 rmux 不可达(rmux_unreachable)与空列表,状态提示不同
- MNT-003: 抽 send_control 复用 send→状态校验→解析;call_remote_* 变薄包装
- MNT-005: 移除单变体 GoPublicAction,handle_key 返回 ()
- MNT-007: 新增 tui::nav 共享 step/clamp + EmptyPolicy,Sessions/Config/ui 复用

验证:cargo +nightly test -p lucarned --features tui 142+2 purity 绿;默认 67+2 绿;默认构建无 ratatui/crossterm/rpassword;clippy 干净。
@tuchg

tuchg commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Review position: significant progress, but still do not merge as-is.

最新 head c509935a 相比上一轮确实做了实质收敛:lucarne-web / lucarne-agentbind 独立 crate 已移除,WebChat -> new AgentRuntime 这条 live-agent 分叉看起来被删掉了;archive 收进 lucarne-rmux::archive,TUI 和 gateway 复用同一 store;remote control plane 和 public gateway 分端口隔离;remote_config.rs 也把一部分配置解析从 main.rs 拿出来了。

但这些改动还没有把架构收成一条清晰主线。尤其需要强调:release 入口对齐不等于产品能力应该默认融合。该有 feature 还是要有 feature。 默认 lucarned 把 remote / rmux / termgw / TUI 全部无条件拉进来,不是架构改善,只是把 feature 边界抹掉了。

What improved

  • The old lucarne-web direct runtime bridge is gone, so the most dangerous WebChat -> AgentRuntime::new live-agent split appears removed.
  • The old sidecar lucarne-agentbind crate is gone; terminal-bound agent binding now lives in lucarne::terminal_agent_bind and records observations in ControlPlaneSqliteStore.
  • Archive now has one implementation under lucarne-rmux::archive; both TUI and termgw call that store.
  • Terminal scrollback capture is now bounded to the recent window through SCROLLBACK_CAPTURE_LINES.
  • Public gateway no longer mounts /api/remote/*; remote control routes are served from a distinct loopback-only control listener.
  • Quick Tunnel smoke coverage was added as an env-gated harness.

Those are real improvements. They do not yet resolve the main architecture issues below.

Remaining architecture issues

  • [P1] Default/product fusion removed the feature boundary instead of fixing release packaging. crates/lucarned/Cargo.toml now has default = [], but lucarne-remote, lucarne-termgw, lucarne-rmux, ratatui, crossterm, qrcode, and reqwest blocking are unconditional dependencies. A release binary may choose to build a named product bundle feature, but the source architecture still needs explicit features such as terminal/rmux, terminal-gateway, remote-access, TUI, and any convenience bundle. Otherwise no-default/default builds can no longer protect the base daemon from remote/rmux/TUI drift.

  • [P1] Remote is still terminal-gateway-shaped rather than a reusable middle layer. DaemonRemoteControl::ensure_gateway still connects RmuxMonitor, adopts rmux sessions, builds lucarne_termgw::router_with_pool_and_store, then starts the tunnel. The request path is still effectively remote start -> rmux monitor -> termgw -> tunnel, not RemoteAccess -> Admission -> Operator API -> capability.

  • [P1] Terminal-bound agent control is not the same as LucarneCore live session control. /agent/{id} mirrors a provider transcript and writes prompts into an interactive rmux pane. That can be a valid terminal-adjacent feature, but it must be named and tested as such. It is not Operator -> LucarneCore -> AgentRuntime open/resume/submit/approve/interrupt/close.

  • [P1] The new live-journey coverage is mostly a manifest, not an executed journey. terminal_live_journey_manifest.rs names required gates, but it does not drive the actual local/remote/channel user journeys. The Quick Tunnel harness checks public auth, route isolation, session list, and read-only write refusal, but it does not cover live agent open/resume/submit/stream/approve/interrupt/close or prove terminal archive-and-close isolation from LucarneCore.live_sessions.

  • [P1] Transcript hot paths are still not bounded enough. terminal_agent_bind::read_messages(path, 0) is still used for initial agent load and history reads, and read_complete_lines_from does read_to_end from that offset. Incremental reads after the initial offset are better, but initial/history load can still scan whole transcripts. The product-level history/chat API needs tail-window/cursor semantics from day one.

  • [P2] Provider-specific config is improved but not fully back at the provider boundary. remote_config.rs still owns CloudflareFileConfig { token, public_url, binary_path } and merges it when provider == cloudflared. Moving this out of main.rs helps, but daemon config still owns concrete provider field names for legacy compatibility.

  • [P2] Archive single-owner improved, but archive file permissions are still weak. lucarne-rmux::archive::save uses create_dir_all and fs::write with platform defaults. Terminal scrollback can contain secrets; the archive directory/file should be created owner-only on Unix, e.g. 0700 dir / 0600 file.

Required before merge

  1. Restore explicit capability features. Keep a minimal base daemon/default path; add named features for terminal/rmux, terminal-gateway, remote-access, TUI, and a deliberate product bundle if wanted. Release packaging can opt into a bundle, but default source architecture should not erase the boundaries.
  2. Define RemoteAccess as the middle layer and RemoteExposureManager as the control plane. Remote startup must not be structurally tied to rmux/termgw unless the selected exposed capability is terminal.
  3. Clarify terminal-bound agent semantics: distinguish rmux pane running an agent CLI from LucarneCore live session, and do not let either pretend to own the other.
  4. Replace the live journey manifest with executable E2E coverage for the real user journeys: local/remote ingress, open/resume/submit/stream/approve/interrupt/close, read-only/write refusal, reconnect/disconnect, and archive-and-close isolation from core live sessions.
  5. Make transcript initial/history reads bounded and cursor-based.
  6. Finish archive hardening with owner-only permissions.
  7. Keep provider-specific remote config at the provider descriptor/compat layer, not in daemon/common logic.

Bottom line

This head is materially better than the earlier one, and several previous objections should be downgraded or removed. But the current “Fuse rmux terminal gateway into lucarned” direction overcorrects: it aligns the release entrypoint by fusing product capabilities into the default daemon instead of preserving explicit features and an access/operator architecture. I would still hold merge until features, access-plane ownership, operator API, bounded transcript reads, and real live E2E journeys are in place.

@qinsehm1128 qinsehm1128 changed the title feat: rmux terminal-monitor subsystem + public-access tunnel + lucarned tui dashboard (feature-gated) Fuse rmux terminal gateway into lucarned Jun 2, 2026
@qinsehm1128

Copy link
Copy Markdown
Author

已按 review 总评修复并推送到 head 0dbddb1

处理内容:

  1. 恢复显式 capability features:remote-accessterminal-rmuxterminal-gatewaytuiproduct-terminal。默认 source build 保持 base daemon,release packaging 通过 [package.metadata.dist] features = ["product-terminal"] 选择产品 bundle。
  2. lucarned 默认构建不再链接 lucarne-remote / lucarne-termgw / lucarne-rmux / rmux-sdk / ratatui / crossterm;新增 default_build_fusion guard 同时验证默认排除、product-terminal 包含,以及单独 feature 编译不意外耦合。
  3. RemoteAccess 语义改为中间层:DaemonRemoteControl 重命名为 RemoteExposureManager,增加 ExposedCapability::TerminalGateway,startup 路径改为先准备 selected capability,再由 provider tunnel publish loopback listener;control listener 明确为 off-tunnel dedicated loopback listener。
  4. terminal-bound agent 语义已明确为 terminal-adjacent provider transcript / rmux pane binding,不再宣称等同于 LucarneCore live session;contract test 也验证 archive-and-close 不触碰 LucarneCore / live_sessions
  5. 删除 checklist-only terminal_live_journey_manifest.rs,替换为可执行 terminal_live_journey_contract.rs,要求本地可复跑测试 / harness 证据存在,包括 Quick Tunnel harness、read-only refusal、bounded transcript、owner-only archive、core isolation。
  6. transcript 初始/history 读取改为 bounded tail window,增量读取也使用 bounded reader,并保留 cursor / partial-line 语义。
  7. archive 存储改为 Unix owner-only:目录 0700,文件 0600,并补测试。
  8. provider-specific compat config 下沉到 provider descriptor:Cloudflared 自己声明 deprecated remote.cloudflare alias;daemon config 只保留 opaque extra_sections + generic providers.<id> map,不再拥有 CloudflareFileConfig concrete struct。
  9. 文档同步为“单一入口 + 显式 source features + release bundle”,修正之前“默认无条件融合”的表述。

验证已通过:

  • cargo +nightly fmt --check
  • git diff --check
  • cargo +nightly check -Zbuild-dir-new-layout -p lucarned
  • cargo +nightly check -Zbuild-dir-new-layout -p lucarned --features remote-access
  • cargo +nightly check -Zbuild-dir-new-layout -p lucarned --features terminal-gateway
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarned
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarned --features product-terminal
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarned --features product-terminal --test default_build_fusion
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarned --features product-terminal remote::tests
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarned --features product-terminal remote_config
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarne --features terminal-agent-bind terminal_agent_bind --lib
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarne --features terminal-agent-bind --test terminal_live_journey_contract
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarne-rmux archive
  • cargo +nightly test -Zbuild-dir-new-layout -p lucarne-remote cloudflared_declares_legacy_config_section_alias
  • bash -n scripts/remote-quick-tunnel-e2e.sh && scripts/remote-quick-tunnel-e2e.sh(env-gated harness skip path)
  • cargo +nightly clippy -Zbuild-dir-new-layout --workspace --all-targets --all-features --exclude agent-sessions --no-deps -- -D warnings
  • cargo +nightly test -Zbuild-dir-new-layout --workspace --all-features --exclude agent-sessions

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