Code Reader
首页
帮助
设计文档
首页
帮助
设计文档
  • PTY (Pseudo Terminal)

PTY (Pseudo Terminal)

PTY 提供了虚拟终端功能,支持在 TUI 中运行命令。

概述

PTY 使用 bun-pty 库创建伪终端会话,支持多个客户端同时连接到同一个终端会话。PTY 会话可以创建、更新、调整大小和删除。

定义位置

packages/opencode/src/pty/index.ts:18-64

主要对象

Info

PTY 会话信息。

| 属性名 | 类型 | 必填 | 说明 | | --------- | --------- | -------- | -------------- | -------- | | id | string | 是 | PTY 唯一标识符 | | title | string | 是 | 显示标题 | | command | string | 是 | 执行的命令 | | args | string[] | 是 | 命令参数列表 | | cwd | string | 是 | 工作目录 | | status | "running" | "exited" | 是 | 会话状态 | | pid | number | 是 | 进程 ID |

CreateInput

创建 PTY 会话的输入。

属性名类型必填说明
commandstring否执行的命令(默认使用 shell)
argsstring[]否命令参数
cwdstring否工作目录(默认项目目录)
titlestring否显示标题
envRecord<string, string>否环境变量

UpdateInput

更新 PTY 会话的输入。

属性名类型必填说明
titlestring否新的标题
sizeobject否新的终端大小
size.rowsnumber-行数
size.colsnumber-列数

TypeScript 类型定义

export type Info = {
  id: string
  title: string
  command: string
  args: string[]
  cwd: string
  status: "running" | "exited"
  pid: number
}

export type CreateInput = {
  command?: string
  args?: string[]
  cwd?: string
  title?: string
  env?: Record<string, string>
}

export type UpdateInput = {
  title?: string
  size?: {
    rows: number
    cols: number
  }
}

事件

事件名类型说明
pty.createdinfo: InfoPTY 创建
pty.updatedinfo: InfoPTY 更新
pty.exited{id, exitCode}PTY 退出
pty.deleted{id}PTY 删除

常量配置

const BUFFER_LIMIT = 1024 * 1024 * 2 // 2MB 缓冲区限制
const BUFFER_CHUNK = 64 * 1024 // 64KB 发送块大小

典型使用场景

1. 创建 PTY 会话

const pty = await Pty.create({
  command: "bash",
  cwd: "/home/user/project",
  title: "Bash Session",
})

console.log(pty.id) // pty_xxx
console.log(pty.status) // "running"

2. 使用默认 Shell

// 不指定命令,使用默认 shell
const pty = await Pty.create({
  cwd: "/home/user/project",
  title: "Terminal",
})

// 自动使用 shell.preferred()

3. 创建带参数的命令

const pty = await Pty.create({
  command: "npm",
  args: ["test", "--watch"],
  cwd: "/home/user/project",
  title: "Test Runner",
})

4. 列出所有 PTY 会话

const sessions = Pty.list()

for (const session of sessions) {
  console.log(`${session.title}: ${session.status}`)
}

5. 获取 PTY 会话信息

const session = Pty.get(ptyID)

`if (session) {
  console.log(`PID: ${session.pid}`)
  console.log(`Command: ${session.command}`)
  console.log(`Status: ${session.status}`)
}`

6. 更新 PTY 会话

// 更新标题
await Pty.update(ptyID, {
  title: "New Title",
})

// 调整终端大小
await Pty.update(ptyID, {
  size: {
    rows: 40,
    cols: 120,
  },
})

7. 删除 PTY 会话

await Pty.remove(ptyID)

// 这会:
// 1. 终止进程
// 2. 关闭所有 WebSocket 连接
// 3. 从状态中删除
// 4. 发布 pty.deleted 事件

8. 连接客户端到 PTY

const handlers = Pty.connect(ptyID, wsContext)

if (handlers) {
  // 处理客户端消息
  ws.onMessage((message) => {
    handlers.onMessage(message)
  })

  // 处理客户端断开
  ws.onClose(() => {
    handlers.onClose()
  })
}

9. 调整 PTY 大小

// 快速调整大小
Pty.resize(ptyID, 120, 40)

10. 写入 PTY

// 发送输入到 PTY
Pty.write(ptyID, "ls -la\n")

11. 监听 PTY 事件

Bus.subscribeAll(async (event) => {
  switch (event.type) {
    case "pty.created":
      const { info } = event.properties
      console.log(`PTY created: ${info.title}`)
      break

    case "pty.exited":
      const { id, exitCode } = event.properties
      console.log(`PTY ${id} exited with code ${exitCode}`)
      break

    case "pty.deleted":
      console.log(`PTY ${event.properties.id} deleted`)
      break
  }
})

PTY 会话生命周期

客户端连接

WebSocket 连接

// 当客户端连接时
const handlers = Pty.connect(ptyID, ws)

// 处理器包含:
handlers.onMessage // 接收客户端输入
handlers.onClose // 客户端断开

数据转发

缓冲区管理

输出缓冲

PTY 维护一个输出缓冲区:

buffer: string

// 当有新数据时
session.buffer += data

// 限制缓冲区大小
if (session.buffer.length > BUFFER_LIMIT) {
  session.buffer = session.buffer.slice(-BUFFER_LIMIT)
}

新客户端连接

当新客户端连接时,发送当前缓冲区:

if (session.buffer) {
  // 分块发送,避免过大消息
  for (let i = 0; i < buffer.length; i += BUFFER_CHUNK) {
    ws.send(buffer.slice(i, i + BUFFER_CHUNK))
  }
}

环境变量

PTY 会话自动包含以下环境变量:

{
  ...process.env,
  TERM: "xterm-256color",  // 启用 256 色
  ...input.env,               // 用户指定的环境变量
}

Shell 处理

如果命令以 sh 结尾,自动添加 -l 标志:

if (command.endsWith("sh")) {
  args.push("-l")
}

最佳实践

  1. 资源清理: 始终在退出时调用 remove()
  2. 缓冲区限制: 尊意 BUFFER_LIMIT 避免内存泄漏
  3. 错误处理: 捕获并记录 PTY 错误
  4. 并发安全: 使用状态管理避免竞争条件
  5. 事件监听: 订阅 PTY 事件以更新 UI

性能考虑

批量发送

// 分批发送大缓冲区
const CHUNK_SIZE = 64 * 1024

for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
  ws.send(buffer.slice(i, i + CHUNK_SIZE))
}

活动客户端检查

// 检查 WebSocket 状态
ws.readyState !== 1 // 不等于 OPEN

对象关系

变更历史

版本变更内容日期
v1初始 PTY 实现-
v1.1添加缓冲区限制和分块发送-
v1.2改进客户端连接管理-

相关文档

  • Session - 会话对象
  • Events - 事件系统