Skip to content

fix(terminal): Ctrl+C and the timeout cancel switch the active session, and can miss their target #1394

Open

Description

@BuddhaDiedLaughing

变更说明 / Description

Terminal.sendInterruptSignal(sessionId) 不是向指定会话发送中断,而是先把全局"当前会话"切到该 id,再对
"当前会话"发送。中断的目标因此在协程挂起之后才从全局状态解析:

app/src/main/java/com/ai/assistance/operit/core/tools/system/Terminal.kt:193-196

fun sendInterruptSignal(sessionId: String) {
    terminalManager.switchToSession(sessionId)
    terminalManager.sendInterruptSignal()
}

TerminalManager.sendInterruptSignal()(OperitTerminalCore,TerminalManager.kt:410-421)随后在
Dispatchers.IO 上启动协程,读取 sessionManager.getCurrentSession() 后写入 "\u0003"。

四个可观测后果 / What follows

  1. 前台终端被切走。 后台会话或 hidden exec 超时取消时,可见终端会跳到那个会话。
  2. 中断可能落到别的 pty。 switchToSession 与写入是两步,中间隔一次协程启动;期间任何切换都会改写目标。
  3. 未知 id 静默失败。 SessionManager.switchToSession 只记一条 Session not found: <id> 并返回 false
    (SessionManager.kt:56-72),调用方丢弃返回值,中断落到当前会话上。
  4. 没有当前会话时是空操作。 currentSession?.let { ... } 什么都不发也不报错,而
    StandardTerminalCommandExecutor.cancelTimedOutCommand 仍在等该会话 settle(605-617)。

同形写法也出现在 sendInput(sessionId, input)(Terminal.kt:185-188)。

字节本身没有问题 / The byte is fine

"\u0003" 经 terminalSession.stdin.writer() 写入是单个 0x03;实测 pty 的 stty -a 为
isig icanon iexten echo,行规程会把它转成 SIGINT。Pty.kt 只读取这些标志(getTerminalFlags),从不清除
ISIG。所以问题只在路由,不在编码。

建议 / Proposed fix

在核心侧新增按会话的重载:解析 sessionManager.getSession(sessionId),为空则告警返回,否则把 "\u0003"
写入该会话 writer,不触碰全局状态;应用侧 Terminal.sendInterruptSignal(sessionId) 直接调用该重载。

注意:OperitTerminalCore#4(开放中,2026-08-22 起)正在重写 TerminalManager.kt / Pty.kt,若更合适,
这个修复可以直接并入 #4。#4、#6、#7、#8 都没有触及 sendInterruptSignal 或 switchToSession,因此不与现有
工作重复。

验证 / Verification

两个会话,在后台会话触发超时取消,断言:前台会话 id 不变;后台命令在 settle 超时内收到完成事件;日志中不再出现
cancellation did not settle。UI 上的 Ctrl+C 仍中断可见会话;sendInterruptSignal("不存在") 只记录告警。

来源 / Provenance

在本地 Operit 1.12.x 上从 main 源码读到,并用 stty -a 实测 pty 标志;未在本机编译验证。

Activity

  1. yoruuuchan commented on Oct 22, 2026

    @yoruuuchan
    Contributor

    认领。已核对现有工作,避免重复:

    计划:

    • OperitTerminalCore:新增按 sessionId 定向的入口 sendInputToSession / sendInterruptSignalToSession,直接解析目标会话的 pty writer 写入,不读写全局当前会话;无效 id 与未就绪会话返回明确结果(不写入任何其他会话)。附 JVM 回归测试。
    • Operit:Terminal.sendInput/sendInterruptSignal(sessionId) 去掉「先 switchToSession 再发送」的写法,直接调用定向入口;后台超时取消和 control=ctrl+c 改用该入口并处理返回结果。
    • 更新 terminal 子模块指针,并把 :terminal:testDebugUnitTest 接入 CI,让这组回归测试真正跑起来。

    核心 PR 会附在本 Issue 下,主仓库 PR 关联 Fixes #1394,目标分支 dev。

  2. yoruuuchan commented on Oct 22, 2026

    @yoruuuchan
    Contributor

    修复与核心依赖 PR 已提交:

    核心 PR 的 base 是 feat/performance-terminal-pid——dev 当前的 terminal 指针指向该分支末端提交 0458689,且该分支还没有对应 PR。若另开基于 master 的 PR,本分支的 diff 会把那条 pid 提交一并重放;指到 master 的 PR 合并后 dev 的指针会回退。该分支合入 master 后,这个修复 rebase 过去即可,改动只涉及 TerminalManager.kt 的输入路由和一个新文件。

    验证::terminal:testDebugUnitTest 7 tests / 0 failures,:app:compileDebugKotlin 通过,仓库卫生 / Markdown 链接 / 本地化三项检查 errors=0。多会话真机行为尚未验证,原因已写进 PR。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions