Skip to content

feat(terminal): 会话归 App 所有、标签页、触屏鼠标/粘贴/缩放、终端前台服务 (#183) - #187

Closed
Slacker-LLC wants to merge 8 commits into
mainfrom
feat/terminal-sessions
Closed

Slacker-LLC wants to merge 8 commits into
mainfrom
feat/terminal-sessions

Conversation

@Slacker-LLC

Copy link
Copy Markdown
Owner

Closes #183。base 为 feat/root-only-drop-shizuku(叠在 #185 上),#185 合并后改 base 到 main。与 #186(#182)互相独立,两者都改 CHANGELOG.md 和 06-CURRENT-GAPS.md,后合并的一个需要做一次琐碎的冲突处理。

做了什么

1. 会话归 App 所有

  • 新增 TerminalSessionManager(App 级单例,形式同 AgentTerminals),持有用户终端、它们的 TerminalEmulator 和名称。TerminalScreen 只做附着/分离,onDispose 不再调用 stop();顶栏左上角“返回”也不再停止终端。
  • 分离期间输出照常进 emulator(collector 在 Main dispatcher 上跑,和 UI 同线程),回来时画面和 scrollback 都在。
  • 终端只在这几种情况结束:关闭标签、shell 自己 exit(标签保留,可“重新启动”)、通知里“全部结束”、运行时维护。
  • TerminalForegroundService(独立服务,specialUse,START_NOT_STICKY):用户终端 + Agent 终端里有存活进程时常驻;通知显示“N 个终端运行中”,点击回终端,带“全部结束”;全部结束后停止。TerminalServicePolicy.shouldRun(count) = count > 0,已结束但标签还开着的 shell 不算。docs/AGENT-FOREGROUND-SERVICE.md 已加对应章节,写明不借用 mediaPlayback / dataSync。
  • AgentTerminals 空闲回收:只回收 foregroundState() == IDLE 的 shell。

判断“有程序在跑”(TerminalSession.foregroundState()):

  1. JNI tcgetpgrp()(新增 PtyBridge.foregroundPgid)由唯一持有 fd 的 IO 协程采样;shell 自身进程组在首次输出静默 ≥400 ms 时记为基线。
  2. 交叉校验:guest 每个提示符已经输出 OSC 133;A,所以一旦见过该标记,回车之后没看到新的提示符就算“在运行”。这是为了应对个别 su 自带 pty 中继、PTY 主端的前台进程组永远不变的情况。
  3. 无法判断(UNKNOWN)按“在运行”处理:关闭前确认、Agent 不回收。

2. 标签页

顶部标签栏:+ 新建、点选切换、× 关闭、长按重命名、状态点(沿用 OSC 7501 的 working / blocked / idle / done / error)。上限 8,和 Agent 的上限分开。每个标签保留自己的 sessionId,workspace 语义不变(硬规则 6);聊天里“打开终端”会复用该聊天已有的存活 shell,带命令的永远开新标签。关闭有前台程序的标签会确认一次,空闲的直接关。操作按钮都是文字按钮。Agent 的终端以只读“Agent · tN”标签出现,“接管”后可输入。

3. 触屏

  • 鼠标上报:DECSET 1000 / 1002 / 1003,编码 SGR 1006(不支持 1006 的程序用 X10,坐标 ≤223)。点击发按下+释放;横向拖动在 1002/1003 下发拖动,竖向滑动按行折算成滚轮;长按仍是文本选择。
  • 没开鼠标模式的全屏程序:竖向滑动转 ↑/↓,遵守 DECCKM(ESC O A/B 或 ESC [ A/B),带余量累积,单次最多 12 个。
  • 双指捏合:字号 8–32 sp,写入 TerminalPrefs,缩放后重算行列并通过 pty 通知程序。
  • 粘贴:选择菜单里和长按空白处都有;程序开了 DECSET 2004 就用括号粘贴包裹,内容里的 ESC 会被丢弃,所以粘贴内容无法带出结束标记。

与 issue 的差异(请注意)

  • 运行时维护的提示是“停止时告知”,不是“停止前确认”。 TerminalSession.stopAll* 的调用方在 rootfs 修复、挂载变更这类非 UI 的运行时层,那里没有可以等用户回答的位置,而且 rootfs 损坏时修复不能被推迟。实现:停止前通知管理器,Terminal 页弹一次对话框,页面不在前台时发一条通知。没有做“先确认再维护”,要做需要由发起维护的 UI 自己先看 busyCount(),目前没有这样的入口,已写入 06-CURRENT-GAPS.md。
  • Agent 标签是文本快照(无颜色、无光标位置),不是第二个 emulator 视图:Agent 终端的 emulator 在 IO 线程上喂,直接共享给 UI 线程渲染不安全。

验证

  • :app:testDebugUnitTest 全量通过(LANG=C.UTF-8)。第一次全量运行里 AgentTerminalsTest.wait_for matches on the screen(既有用例,真实 dispatcher + 3 s 上限)失败过一次,单独跑和第二次全量都通过,判断为并发负载下的时序抖动,不是本 PR 引入;没有改它。
  • 新增测试:
    • TerminalMouseTest:SGR 坐标和按钮值、X10 上限、DECCKM 两种方向键序列、括号粘贴与 ESC 剥离、字号夹取。
    • TerminalSessionManagerTest:分离 ≠ 停止、输出和 scrollback 在附着/分离中保留、关闭、shell 自行退出标签保留并可重启、全部结束、标签上限、每个标签的 sessionId 与重命名、空闲/忙碌判断、保活计数、维护通知、openOrSelect 语义。
    • TerminalForegroundStateTest:前台组变化 → BUSY;组不变但回车后没有提示符 → BUSY;提示符回来 → IDLE;没有提示符钩子时只看进程组;标记被切在两次读取之间;已停止的会话是 IDLE。
    • AgentTerminalsReclaimTest(否定用例):前台有程序的终端放 5 小时也不被回收;状态未知的不被回收;1 小时内用过的不被回收;只有空闲 shell 才被回收。
    • TerminalServicePolicyTest:没有存活进程就不常驻。
    • scripts/test_pty_bridge.py:在 Linux 上用真实 JVM、真实 PTY 和子进程跑生产 C 代码,验证 foregroundPgid:空闲 shell 等于自身进程组,前台有程序时不等,坏 fd 返回 -1。
  • :app:lintDebug:0 error。:app:minifyReleaseWithR8:通过。provenance / build-cleanup / runtime package boundary 守卫通过,字符串审计没有新增缺译或格式不一致(8 种语言)。

未验证(需要真机)

  • 小米 24129PN74C / HyperOS 切到后台 30 分钟以上终端是否存活,以及前台服务通知在该机型上的表现。
  • Android NDK / bionic 上的 tcgetpgrp 构建与运行(只在 Linux glibc 宿主验证过)。
  • 实际使用的 su(KernelSU / Magisk / APatch)是否把 PTY 直接交给 shell。如果 su 自带 pty 中继,只剩 OSC 133;A 一个信号;用户在 .bashrc 里替换 PROMPT_COMMAND 时两个信号都失效,终端会被当作空闲(关闭不提示,Agent 空闲回收可能关掉它)。
  • vim(mouse=a)、htop、tmux(mouse on)、less / man 里点击与滚动的手感;捏合后 tput cols 随之变化;往真实 bash 粘贴多行不逐行执行。
  • 前台后台 job(cmd &)不算前台程序,关闭标签不会提示。

已知限制(第二阶段)

系统或 OEM 杀掉 Minis 进程、升级 App 时,PTY 主端随进程关闭,shell 收到 SIGHUP,任务照样会断。需要把 PTY 持有者放到 App 进程之外(guest 里的 tmux / dtach,重启后重新附着)。本 PR 不做,TerminalSessionManager 作为唯一持有者,不堵这条路。

🤖 Generated with Claude Code

https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3


Generated by Claude Code

Slacker-LLC and others added 5 commits October 10, 2026 07:05
…reen on a libsu root service

Root is the app's one privileged path. The code before this change is
archived on the branch archive/before-root-only (6a6b450); the rootless
PRoot + Ubuntu backend is shelved as #184.

- android-shizuku-cli becomes android-root-cli: the same subcommands and
  JSON, run as root through RootProcess -> DirectRootRunner (su). The
  shizuku_cli permission key migrates to root_cli; a stale
  android-shizuku-cli wrapper in an existing rootfs is removed on the next
  guest CLI install.
- The virtual screen service is hosted by a libsu RootService
  (VirtualScreenRootService, daemon mode) instead of a Shizuku UserService,
  and accepts uid 0 only. The Shizuku-reserved destroy() leaves the AIDL;
  libsu ends the daemon on app update, so no service version is needed.
  Probes saved under the Shizuku host no longer count (host marker in the
  fingerprint): the self-check must pass again before it is re-enabled.
- Accessibility repair, clearing restricted settings and one-tap authorize
  use Root. Settings > System gets a Root page in place of "Root & Shizuku";
  the readiness checklist drops Shizuku.
- Removed: ShizukuManager/ShizukuBackend, the Shizuku settings page, the
  Shizuku API/provider dependency, manifest provider/permission/queries,
  and every Shizuku string in all eight locales (new strings translated).
- Contracts: AGENTS.md rule 3 and 04 list android-root-cli (with its raw
  `exec` root shell) and the virtual-screen root service; 05 describes the
  new VScreen host; 06 records what is unverified on a device and two gaps
  (MCP can reach android-root-cli through linux.shell; #182 not built).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
…design

The remote MCP path stays gated by the one-shot MCP_CONFIRM approval, which
AutonomyMode (全自动) does not bypass, and by the default-off root_cli switch.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
…ice for terminals (#183)

Terminals belong to the app (TerminalSessionManager); the Terminal page only
attaches and detaches. Tabs, mouse reporting, swipe-to-arrow-keys, pinch zoom,
bracketed paste, a specialUse foreground service while any terminal has a live
process, a prompt-marker cross-check for the foreground detection, and an
idle-reclaim that never closes a terminal with a program running.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Base automatically changed from feat/root-only-drop-shizuku to main October 10, 2026 13:58
main carries #185 as rebased commits (d57ee95, 284b87a) whose tree is byte-identical
to the #185 commits this branch was built on (b8b8727); only the commit ids differ,
which is what made git report conflicts. Tree of this branch is unchanged by the merge.
#186, #188 and #189 landed on main. The merge was clean (CHANGELOG, 06-CURRENT-GAPS
and the string files only had adjacent additions); CI on this head tests the combination.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Only CHANGELOG.md conflicted (both sides added bullets at the end of Unreleased); both kept.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PV4ECd3Yzu9k81sw7vHjS3
Slacker-LLC added a commit that referenced this pull request Oct 11, 2026
* feat: Antigravity 登录、Claude 登录修复、终端大改(并入 0.0.3,含 #187)

- Antigravity:Gemini 请求体走 Cloud Code 信封,账号 OAuth(项目发现/onboarding)、模型列表;OAuth 客户端 id/secret 来自私有构建配置。
- Claude 登录:发布版不再把它编译掉;claude.ai token 地址、共用 CLI 指纹(UA 2.1.280)、前台交换授权码、流中途 error 帧、Claude 5 thinking 关闭、登录进度与失败提示。
- 终端:会话归 App 所有、标签页、触屏鼠标/粘贴/缩放、前台服务(#187);惯性滑动、滚动条、选词与扩选、拼音预编辑、标签标题、辅助栏第二页;模拟器对照 Termux(行数变化保光标行、软换行与重排、Unicode 15 宽度、冒号子参数、DCS 跳过、UTF-8 OSC、DEC 线框、OSC 52/8、响铃、反色修复);Minis 操作终端的光标/模式/高亮标记、until_prompt、find、click/scroll/resize 与启动查询应答。

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

* fix: translations for the new sign-in strings; the no-secret test only looks at the Antigravity source

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>

---------

Co-authored-by: limuzi013 <[email protected]>
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
@Slacker-LLC

Copy link
Copy Markdown
Owner Author

已并入 #195(squash 合并,含本 PR 的全部提交),随 0.0.3 一起发布。

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