{"_id":"@ai-zen/socket-pty","_rev":"3-5b1fc20c23915cc20b95df027587d571","name":"@ai-zen/socket-pty","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@ai-zen/socket-pty","version":"0.1.0","keywords":["pty","terminal","socket","conpty","process"],"license":"MIT","_id":"@ai-zen/socket-pty@0.1.0","maintainers":[{"name":"lzqcn","email":"by.lzq@qq.com"}],"homepage":"https://github.com/ai-zen/socket-pty#readme","bugs":{"url":"https://github.com/ai-zen/socket-pty/issues"},"bin":{"socket-pty":"dist/cli.js"},"dist":{"shasum":"483da4a8588d39638a4de572e6202b6bfdd3bd15","tarball":"https://registry.npmjs.org/@ai-zen/socket-pty/-/socket-pty-0.1.0.tgz","fileCount":29,"integrity":"sha512-3XzMhIC0PigJF+FwKxnwiDjVUmDWOTrtR0ewjIGg5gFzElv8eg1ONj6vXGIYi3AD0GBs8A60RZkueH12bAQDbA==","signatures":[{"sig":"MEQCIHsQlRs6RQJID5Hi58x4xpTOHMlqZJjjKWeiErABXiobAiANcEzxO/sWMyURfNJUwBSOM4ufvXGoeDQwpkn2TeczjA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":105924},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./mcp":{"types":"./dist/mcp/index.d.ts","import":"./dist/mcp/index.js"},"./package.json":"./package.json"},"gitHead":"bc11750dfd083963cf9faf3e6563f26d7934f0b3","scripts":{"test":"npm run build && node scripts/self-test.mjs && node scripts/id-test.mjs && node scripts/mcp-test.mjs && node scripts/mcp-smoke.mjs","build":"tsc","clean":"rimraf ./dist","prebuild":"npm run clean","prepublishOnly":"npm run test"},"_npmUser":{"name":"lzqcn","email":"by.lzq@qq.com"},"repository":{"url":"git+ssh://git@github.com/ai-zen/socket-pty.git","type":"git"},"_npmVersion":"11.13.0","description":"Lightweight PTY transport exposed over a socket — anyone can connect and read/write a managed terminal process. Cross-platform (Windows ConPTY via node-pty).","directories":{},"_nodeVersion":"24.16.0","dependencies":{"zod":"^3.25 || ^4.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"rimraf":"^6.0.0","typescript":"^5.3.3","@types/node":"^24.12.2"},"optionalDependencies":{"node-pty":"^1.1.0"},"_npmOperationalInternal":{"tmp":"tmp/socket-pty_0.1.0_1785774068132_0.5945090151644596","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ai-zen/socket-pty","version":"0.2.0","keywords":["pty","terminal","socket","json-rpc","jsonrpc","mcp","conpty","process","pseudo-terminal"],"license":"MIT","_id":"@ai-zen/socket-pty@0.2.0","maintainers":[{"name":"lzqcn","email":"by.lzq@qq.com"}],"homepage":"https://github.com/ai-zen/socket-pty#readme","bugs":{"url":"https://github.com/ai-zen/socket-pty/issues"},"bin":{"socket-pty":"dist/cli.js"},"dist":{"shasum":"8a8845436aeb91c39deaf35b6d0bc664343c03ab","tarball":"https://registry.npmjs.org/@ai-zen/socket-pty/-/socket-pty-0.2.0.tgz","fileCount":25,"integrity":"sha512-XiPLWioE+VVtxodV8sFaFuXZhO2btwIdkr8cF0bLwqUfzEs0tLhX18YgDi8RAXhJ05r19IjWtT7B5zS/SCoEPw==","signatures":[{"sig":"MEQCIHFJhGSqh3eQeTAYCzVwgPFrWPKa2y1Sqgt7EFkJ8XHUAiAom71re9WC3rZK8xUR7ZbYFVm0wSeHvcLkBuVqg4LVZg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107783},"main":"dist/index.js","type":"module","_from":"file:ai-zen-socket-pty-0.2.0.tgz","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./mcp":{"types":"./dist/mcp/index.d.ts","import":"./dist/mcp/index.js"},"./package.json":"./package.json"},"scripts":{"test":"npm run test:unit && npm run build && node scripts/self-test.mjs && node scripts/id-test.mjs && node scripts/mcp-test.mjs && node scripts/tcp-spawn-test.mjs && node scripts/mcp-smoke.mjs && node scripts/cli-e2e.mjs && node scripts/pure-jsonrpc-test.mjs","build":"tsc","clean":"rimraf ./dist","prebuild":"npm run clean","test:unit":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"lzqcn","email":"by.lzq@qq.com"},"_resolved":"C:\\Users\\bylzq\\AppData\\Local\\Temp\\c67ff3894d36ac8886e76393b6f06ce9\\ai-zen-socket-pty-0.2.0.tgz","_integrity":"sha512-XiPLWioE+VVtxodV8sFaFuXZhO2btwIdkr8cF0bLwqUfzEs0tLhX18YgDi8RAXhJ05r19IjWtT7B5zS/SCoEPw==","repository":{"url":"git+ssh://git@github.com/ai-zen/socket-pty.git","type":"git"},"_npmVersion":"11.3.0","description":"Cross-platform PTY transport exposed over a socket via a standard JSON-RPC 2.0 protocol — any client that speaks JSON-RPC can read/write a managed terminal process. Renders the real terminal screen via xterm-headless. MCP stdio management included as a se","directories":{},"_nodeVersion":"24.1.0","dependencies":{"zod":"^3.25 || ^4.0","node-pty":"^1.1.0","@xterm/headless":"^6.0.0","@modelcontextprotocol/sdk":"^1.30.0"},"_hasShrinkwrap":false,"devDependencies":{"rimraf":"^6.0.0","vitest":"^4.1.10","typescript":"^5.3.3","@types/node":"^24.12.2","@vitest/coverage-v8":"^4.1.10"},"_npmOperationalInternal":{"tmp":"tmp/socket-pty_0.2.0_1785788238556_0.9996241220125659","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@ai-zen/socket-pty","version":"0.2.1","description":"Cross-platform PTY transport exposed over a socket via a standard JSON-RPC 2.0 protocol — any client that speaks JSON-RPC can read/write a managed terminal process. Renders the real terminal screen via xterm-headless. MCP stdio management included as a se","type":"module","main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./mcp":{"types":"./dist/mcp/index.d.ts","import":"./dist/mcp/index.js"},"./package.json":"./package.json"},"bin":{"socket-pty":"dist/cli.js"},"scripts":{"clean":"rimraf ./dist","prebuild":"npm run clean","build":"tsc","test":"npm run test:unit && npm run build && node scripts/self-test.mjs && node scripts/id-test.mjs && node scripts/mcp-test.mjs && node scripts/tcp-spawn-test.mjs && node scripts/mcp-smoke.mjs && node scripts/cli-e2e.mjs && node scripts/pure-jsonrpc-test.mjs","test:unit":"vitest run","test:coverage":"vitest run --coverage","test:watch":"vitest","prepublishOnly":"npm run test"},"keywords":["pty","terminal","socket","json-rpc","jsonrpc","mcp","conpty","process","pseudo-terminal"],"license":"MIT","engines":{"node":">=18"},"repository":{"type":"git","url":"git+ssh://git@github.com/ai-zen/socket-pty.git"},"homepage":"https://github.com/ai-zen/socket-pty#readme","bugs":{"url":"https://github.com/ai-zen/socket-pty/issues"},"devDependencies":{"@types/node":"^24.12.2","@vitest/coverage-v8":"^4.1.10","rimraf":"^6.0.0","typescript":"^5.3.3","vitest":"^4.1.10"},"dependencies":{"@modelcontextprotocol/sdk":"^1.30.0","@xterm/headless":"^6.0.0","zod":"^3.25 || ^4.0","node-pty":"1.2.0-beta.15"},"gitHead":"ab462a26a0da9bc04359e66ac1c71a271fe40935","_id":"@ai-zen/socket-pty@0.2.1","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-y4Osagqs0kCFZ/r1ZoKg4Uq1ikR49z6kUg2Ak1UuMT0+gzVUsk7p9inr3Zuq173e5J2qTDNrUJsdZ/ok4wchpQ==","shasum":"481b41ee5d5d3090486edc14211a09af9063f5bf","tarball":"https://registry.npmjs.org/@ai-zen/socket-pty/-/socket-pty-0.2.1.tgz","fileCount":25,"unpackedSize":107829,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDQm43KCNYFlmPcy7J86960O/iLkQbqyS6ODjOQj24K1QIhALELLpLNEGU+zM+zPXy/2w9JH+cip41D3qBeSwDv2egk"}]},"_npmUser":{"name":"lzqcn","email":"by.lzq@qq.com"},"directories":{},"maintainers":[{"name":"lzqcn","email":"by.lzq@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/socket-pty_0.2.1_1785825607446_0.19897652107911168"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T16:21:07.966Z","modified":"2026-08-04T06:40:07.778Z","0.1.0":"2026-08-03T16:21:08.287Z","0.2.0":"2026-08-03T20:17:18.703Z","0.2.1":"2026-08-04T06:40:07.600Z"},"bugs":{"url":"https://github.com/ai-zen/socket-pty/issues"},"license":"MIT","homepage":"https://github.com/ai-zen/socket-pty#readme","keywords":["pty","terminal","socket","json-rpc","jsonrpc","mcp","conpty","process","pseudo-terminal"],"repository":{"type":"git","url":"git+ssh://git@github.com/ai-zen/socket-pty.git"},"description":"Cross-platform PTY transport exposed over a socket via a standard JSON-RPC 2.0 protocol — any client that speaks JSON-RPC can read/write a managed terminal process. Renders the real terminal screen via xterm-headless. MCP stdio management included as a se","maintainers":[{"name":"lzqcn","email":"by.lzq@qq.com"}],"readme":"# @ai-zen/socket-pty\n\n![version](https://img.shields.io/badge/version-0.2.1-blue) ![protocol](https://img.shields.io/badge/protocol-JSON--RPC%202.0-green) ![license](https://img.shields.io/badge/license-MIT-blue) ![platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey)\n\n通过 socket（JSON-RPC 2.0）暴露一个可托管的终端进程。启动一个真实终端进程（bash、vim、node REPL 等）后，它会成为网络端点；任何能建立 socket 连接并遵循 JSON-RPC 2.0 的客户端，均可对该终端进行读写。\n\n## 目录\n\n1. [安装](#安装)\n2. [依赖](#依赖)\n3. [组成与入口](#组成与入口)\n4. [端点与地址](#端点与地址)\n5. [启动服务](#启动服务)\n6. [连接与协议](#连接与协议)\n7. [数据包格式](#数据包格式)\n8. [真屏幕与 read 截取](#真屏幕与-read-截取)\n9. [write 支持的键名](#write-支持的键名)\n10. [程序化 API](#程序化-apitypescript)\n11. [MCP 适配层](#mcp-适配层)\n12. [会话生命周期](#会话生命周期)\n13. [已知限制](#已知限制)\n14. [许可证](#许可证)\n\n---\n\n## 安装\n\n```bash\nnpm install @ai-zen/socket-pty\n```\n\n在仓库内本地使用时，先构建并经由 `dist/` 引入：\n\n```bash\nnpm install\nnpm run build\nnode dist/cli.js serve --cmd bash --socket /tmp/x.sock\n```\n\n## 依赖\n\n- **`node-pty`（必需）**：pty 后端。Linux 无预编译二进制，安装时从源码编译，需要 g++ 支持 C++20（`gcc-10` 及以上）。Windows / macOS 提供预编译二进制，开箱即用。\n- **`@xterm/headless`（必需）**：屏幕渲染后端，纯 JS 无编译负担。\n\n`node-pty` 是不可替代的硬依赖，不存在降级后端。\n\n---\n\n## 组成与入口\n\n包提供两个入口：\n\n| 入口 | 内容 | 用途 |\n|------|------|------|\n| `@ai-zen/socket-pty`（主入口 `.`） | `PtyServer` / `PtySession` / `createSocketPty` / 协议与类型 | 以程序化方式启动/持有终端服务 |\n| `@ai-zen/socket-pty/mcp`（`./mcp`） | `MCPManager` / `ConnectionPool` / MCP 适配层 | 作为 MCP server 暴露终端操作工具 |\n\n主入口只暴露能力本体，不含入口适配。MCP 适配层是独立子路径。\n\n---\n\n## 端点与地址\n\n端点（endpoint）是终端服务监听的网络位置，有两种形态：\n\n| 形态 | 描述 |\n|------|------|\n| Unix domain socket | `{ type: \"unix\", path: \"/path/to.sock\" }`，地址形式 `unix:/path/to.sock` |\n| TCP loopback | `{ type: \"tcp\", port: 5174, host: \"127.0.0.1\" }`，地址形式 `tcp:127.0.0.1:5174` |\n\n端点地址**由启动方显式指定**（`--socket <path>` 或 `--port <port>` 二选一）。连接一个终端的前提是已知其地址；服务端**不自动分配**地址——它启动时在调用方指定的地址上监听。\n\n---\n\n## 启动服务\n\n### CLI\n\n```bash\n# Unix domain socket\nsocket-pty serve --cmd bash --socket /tmp/v.sock\n\n# TCP loopback\nsocket-pty serve --cmd bash --port 5174\n\n# Windows：使用 PowerShell / CMD\nsocket-pty serve --cmd powershell.exe --port 5174\nsocket-pty serve --cmd cmd.exe --port 5174\n```\n\n`serve` 选项：\n\n| 选项 | 说明 |\n|------|------|\n| `--cmd <命令>` | 要托管的命令（必填）。Windows 上需带 `.exe` 后缀，如 `powershell.exe` / `cmd.exe`（见[命令与平台](#命令与平台)） |\n| `--socket <路径>` | Unix domain socket 监听路径（与 `--port` 二选一，必给其一） |\n| `--port <端口>` | TCP 监听端口，1-65535 的具体端口（与 `--socket` 二选一，必给其一） |\n| `--cols <n>` | 终端列数（默认 100） |\n| `--rows <n>` | 终端行数（默认 30） |\n| `--cwd <路径>` | 工作目录 |\n| `-h` / `--help` | 帮助 |\n\n`--socket` 与 `--port` 二选一，不能同时给出；两者都不给、或 `--port` 不是 1-65535 的具体端口时，`serve` 报错退出。\n\n启动成功后打印：\n\n```\n[socket-pty] 会话已启动，命令: bash\n[socket-pty] 连接地址: 127.0.0.1:5174\n```\n\n### 程序化\n\n```typescript\nimport { createSocketPty } from \"@ai-zen/socket-pty\";\n\nconst server = createSocketPty({\n  endpoint: { type: \"unix\", path: \"/tmp/v.sock\" }, // 或 { type: \"tcp\", port: 5174, host: \"127.0.0.1\" }\n  command: \"bash\",\n  cols: 100,\n  rows: 30,\n});\nconst address = await server.listen();\n// address: unix:/tmp/v.sock\n```\n\n### 命令与平台\n\n`--cmd` / `command` 指定被托管的命令，由调用方负责传入**当前平台可用**的命令：\n\n| 平台 | 推荐命令 | 说明 |\n|------|----------|------|\n| Linux / macOS | `bash` | 也可传 `zsh`、`sh`、`node -i` 等 |\n| Windows | `powershell.exe` / `cmd.exe` | **必须带 `.exe` 后缀** |\n\nWindows 上 node-pty（ConPTY）对裸命令名**不做 PATH 搜索**：传 `powershell`、`bash` 这类不带 `.exe` 的名字会报 `File not found`。应传带后缀的可执行名，或完整路径（如 `C:\\Program Files\\Git\\bin\\bash.exe`）。\n\n```bash\n# Windows：两种可用写法\nsocket-pty serve --cmd powershell.exe --port 5174\nsocket-pty serve --cmd cmd.exe --port 5174\n```\n\n---\n\n## 连接与协议\n\n### 端点地址形式\n\n- **Unix domain socket**：`unix:/path/to.sock`\n- **TCP**：`tcp:127.0.0.1:5174`\n\n### 帧格式\n\n连接建立后，**每行一条 JSON-RPC 2.0 报文，以 `\\n` 分隔**（换行分帧）。\n\n- 请求必须包含 `id`（`string` 或 `number`）；不支持通知（无 `id` 的请求被拒绝）。\n- 每个请求对应一个响应，响应回显同一个 `id`。\n- `params` 必须是对象。\n\n```\n请求: {\"jsonrpc\":\"2.0\",\"method\":\"pty/read\",\"params\":{},\"id\":7}\n成功: {\"jsonrpc\":\"2.0\",\"result\":{...},\"id\":7}\n失败: {\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"...\"},\"id\":7}\n```\n\n### method 一览\n\n| method | params | result（成功） |\n|--------|--------|----------------|\n| `pty/read` | `{ top? \\| bottom? \\| start?+end? }` | `{ screen, cursor, size }` |\n| `pty/write` | `{ action }` | `{ written }` |\n| `pty/wait` | `{ match?, timeout? }` | `{ matched, screen, cursor, size, waitedMs }` |\n| `pty/status` | `{}` | `{ running, exitCode, pid }` |\n| `pty/resize` | `{ cols, rows }` | `{}` |\n| `pty/kill` | `{}` | `{}` |\n\n### 错误码\n\n| code | 含义 |\n|------|------|\n| `-32700` | 解析错误（非法 JSON） |\n| `-32600` | 无效请求（结构不合法 / 旧 `{op}` 协议被拒绝 / 缺 `id`） |\n| `-32601` | 方法不存在 |\n| `-32602` | 无效参数（如 `text`/`key` 互斥、`top`/`bottom` 互斥、未知键名） |\n| `-32603` | 内部错误 |\n| `-32000` | 服务端错误 |\n\n---\n\n## 数据包格式\n\n以下为每个 method 的完整请求与响应报文。\n\n### `pty/status` — 查询会话状态\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/status\",\"params\":{},\"id\":1}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"running\":true,\"exitCode\":null,\"pid\":1234},\"id\":1}\n```\n\n- `running`：`boolean`，会话是否运行中。\n- `exitCode`：`number | null`，已退出时为退出码，否则为 `null`。\n- `pid`：`number`，被托管进程的 pid。\n\n### `pty/read` — 读取当前屏幕\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/read\",\"params\":{},\"id\":2}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"screen\":\"root@host:~$\\n\",\"cursor\":{\"row\":1,\"col\":14},\"size\":{\"cols\":100,\"rows\":30}},\"id\":2}\n```\n\n- `screen`：`string`，当前终端屏幕的纯文本（已去除 ANSI 控制序列，每行尾部空格被去除，多行以 `\\n` 分隔）。语义为终端**当前真实画面**，非历史日志。\n- `cursor`：`object`，光标位置，`row`/`col` 为 **1-based**（示例中 `root@host:~$` 占 13 字符，光标在其后，`col` 为 14）。\n- `size`：`object`，终端尺寸 `{ cols, rows }`。\n\n`pty/read` 支持截取当前屏的部分行。`top`/`bottom` 与 `start`+`end` 为互斥的两组：\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/read\",\"params\":{\"top\":3},\"id\":3}\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/read\",\"params\":{\"bottom\":3},\"id\":4}\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/read\",\"params\":{\"start\":2,\"end\":5},\"id\":5}\n```\n\n- `top: N` — 取顶部 N 行。\n- `bottom: N` — 取底部 N 行。\n- `start`/`end` — 取 1-based 闭区间 `[start, end]`，必须成对给出。\n- 校验：`top`/`bottom` 互斥；`start`/`end` 必须成对；两组不能混用；越界自动 clamp。\n\n### `pty/write` — 写入数据\n\n`action` 为单个动作对象或动作数组。\n\n写文本：\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/write\",\"params\":{\"action\":{\"text\":\"echo hello\"}},\"id\":6}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"written\":11},\"id\":6}\n```\n\n写按键：\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/write\",\"params\":{\"action\":{\"key\":\"Enter\"}},\"id\":7}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"written\":1},\"id\":7}\n```\n\n动作数组（按序执行多个动作）：\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/write\",\"params\":{\"action\":[{\"text\":\"ls\"},{\"key\":\"Enter\"}]},\"id\":8}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"written\":5},\"id\":8}\n```\n\n- `action` 内 `text` 与 `key` 二选一，不能同时给出；空对象、空数组被拒绝。\n- `written`：`number`，实际写入的 **UTF-8 字节数**（数组动作返回合计）。\n\n### `pty/wait` — 等待某段输出出现\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/wait\",\"params\":{\"match\":\"hello\",\"timeout\":6000},\"id\":9}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"matched\":true,\"screen\":\"hello\\n\",\"cursor\":{\"row\":2,\"col\":1},\"size\":{\"cols\":100,\"rows\":30},\"waitedMs\":45},\"id\":9}\n```\n\n- 在**当前屏幕**中按子串匹配 `match`；未给 `match` 时只按 `timeout` 等待一段固定时长。\n- `timeout`：最大等待毫秒，默认 `10000`。\n- `matched`：`boolean`，是否在超时前匹配到。\n- `screen`：匹配时或超时时的当前屏幕。\n- `waitedMs`：实际耗时（毫秒）。\n\n超时未匹配：\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/wait\",\"params\":{\"match\":\"absent\",\"timeout\":100},\"id\":10}\n← {\"jsonrpc\":\"2.0\",\"result\":{\"matched\":false,\"screen\":\"...\",\"cursor\":{\"row\":1,\"col\":1},\"size\":{\"cols\":100,\"rows\":30},\"waitedMs\":100},\"id\":10}\n```\n\n### `pty/resize` — 调整终端尺寸\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/resize\",\"params\":{\"cols\":120,\"rows\":40},\"id\":11}\n← {\"jsonrpc\":\"2.0\",\"result\":{},\"id\":11}\n```\n\n### `pty/kill` — 终止会话并关闭端点\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/kill\",\"params\":{},\"id\":12}\n← {\"jsonrpc\":\"2.0\",\"result\":{},\"id\":12}\n```\n\n发起 `pty/kill` 后，会话被终止、端点关闭、服务进程退出，当前连接被服务端关闭。\n\n### 错误响应\n\n所有错误响应为统一形状：\n\n```jsonc\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/unknown\",\"params\":{},\"id\":13}\n← {\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found: pty/unknown\"},\"id\":13}\n\n→ {\"jsonrpc\":\"2.0\",\"method\":\"pty/write\",\"params\":{\"action\":{\"text\":\"x\",\"key\":\"Enter\"}},\"id\":14}\n← {\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"write 动作内 text 与 key 只能二选一\"},\"id\":14}\n```\n\n- 错误对象形如 `{ code, message, data? }`，`code`/`message` 见[上文](#连接与协议)。\n- 正常请求的响应回显请求的 `id`；无法解析的报文（连 `id` 都取不到）返回 `id: null`。\n\n---\n\n## 会话流转\n\n以下时序描述一个会话从启动到结束的完整流转：\n\n```mermaid\nsequenceDiagram\n    participant S as 启动方\n    participant P as serve 进程\n    participant C as 客户端\n\n    S->>P: 指定地址并启动（--socket / --port）\n    activate P\n    Note over P: 在指定地址监听\n\n    C->>P: 建立 socket 连接\n    C->>P: {\"jsonrpc\":\"2.0\",\"method\":\"pty/status\",\"id\":1}\n    P-->>C: {\"jsonrpc\":\"2.0\",\"result\":{...},\"id\":1}\n\n    C->>P: {\"jsonrpc\":\"2.0\",\"method\":\"pty/write\",\"params\":{\"action\":[{\"text\":\"ls\"},{\"key\":\"Enter\"}]},\"id\":2}\n    P-->>C: {\"jsonrpc\":\"2.0\",\"result\":{\"written\":5},\"id\":2}\n\n    C->>P: {\"jsonrpc\":\"2.0\",\"method\":\"pty/wait\",\"params\":{\"match\":\"file\",\"timeout\":5000},\"id\":3}\n    P-->>C: {\"jsonrpc\":\"2.0\",\"result\":{\"matched\":true,\"screen\":\"...\",\"id\":3}}\n\n    C->>P: {\"jsonrpc\":\"2.0\",\"method\":\"pty/read\",\"params\":{},\"id\":4}\n    P-->>C: {\"jsonrpc\":\"2.0\",\"result\":{\"screen\":\"...\",\"cursor\":{\"row\":2,\"col\":1},\"size\":{\"cols\":100,\"rows\":30}},\"id\":4}\n\n    C->>P: {\"jsonrpc\":\"2.0\",\"method\":\"pty/kill\",\"params\":{},\"id\":5}\n    P-->>C: {\"jsonrpc\":\"2.0\",\"result\":{},\"id\":5}\n    Note over P: 会话终止，端点关闭，连接被关闭\n    deactivate P\n```\n\n- **启动**：启动方在指定地址监听，地址已知。\n- **连接与请求**：客户端连到该地址，发送 JSON-RPC 2.0 请求行，收到对应响应行。\n- **终止**：`pty/kill` 终止会话并关闭端点。\n\n---\n\n## 真屏幕与 read 截取\n\n- 屏幕由服务端（端点侧）用 xterm-headless 渲染，是终端的**当前真实画面**（所见即所得）。\n- `pty/read` 返回整个当前屏幕（`rows` 行）。它不是历史日志——滚动出屏外的内容不会出现在该返回值中。\n- 可截取当前屏的部分行（`top`/`bottom`/`start`+`end`，见[数据包格式](#数据包格式)）。\n- 连接断开后终端仍运行；重新连接后仍可读到该会话的当前画面。\n\n---\n\n## write 支持的键名\n\n`pty/write` 的 `action.key` 使用以下键名（区分大小写），对应写入的字节序列：\n\n| 类别 | 键名 |\n|------|------|\n| 方向 | `ArrowUp` `ArrowDown` `ArrowLeft` `ArrowRight` |\n| 编辑 | `Enter` `Tab` `Backspace` `Delete` `Home` `End` `PageUp` `PageDown` `Insert` `Escape` `Space` |\n| 功能 | `F1` … `F12` |\n| 组合 | `Ctrl+字母`（`Ctrl+A`…`Ctrl+Z`）、`Shift+字母`、`Alt+字母`、`Alt+Enter`、`Ctrl+Enter`、`Ctrl+Space` 等，可组合修饰符（如 `Ctrl+Alt+Del`） |\n\n- 单字符文本可直接用 `text` 字段。\n- `key` 无法解析时返回 `-32602` 无效参数错误。\n\n---\n\n## 程序化 API（TypeScript）\n\n### `createSocketPty`\n\n```typescript\nimport { createSocketPty } from \"@ai-zen/socket-pty\";\n\nconst server = createSocketPty({\n  endpoint: { type: \"unix\", path: \"/tmp/v.sock\" }, // 或 { type:\"tcp\", port:5174, host:\"127.0.0.1\" }\n  command: \"bash\", // Windows 上请用 \"powershell.exe\" / \"cmd.exe\"（见「命令与平台」）\n  cols: 100,\n  rows: 30,\n  cwd: \"/path/to/workdir\", // 可选\n});\nconst address = await server.listen();\n```\n\n`endpoint` 必填，地址由调用方指定。\n\n### `PtyServer` 实例方法\n\n| 方法 | 说明 |\n|------|------|\n| `listen(): Promise<string>` | 监听端点并创建会话，返回连接地址 |\n| `address(): string` | 实际监听地址 |\n| `session(): IPtySession \\| null` | 当前会话 |\n| `close({ kill? }): Promise<void>` | 关闭端点（`kill: true` 时终止会话），幂等 |\n\n### 会话对象方法\n\n回调 `server.session` 返回 `IPtySession`，提供同步读屏等能力：\n\n```typescript\nconst session = server.session;\nawait session?.readScreen();                       // 整屏\nawait session?.readScreen({ bottom: 5 });          // 底部 5 行\nawait session?.readScreen({ start: 2, end: 6 });   // 第 2..6 行\n```\n\n| 方法 / 属性 | 说明 |\n|------------|------|\n| `readScreen(sel?)` | 读当前屏幕，返回 `{ screen, cursor, size }` |\n| `wait(opts)` | 等待输出，返回 `{ matched, screen, cursor, size, waitedMs }` |\n| `write(data): number` | 写原始数据，返回 UTF-8 字节数 |\n| `resize(cols, rows)` | 调整尺寸 |\n| `kill(signal?)` | 终止 |\n| `pid` / `running` / `exitCode` | 进程信息 |\n\n---\n\n## MCP 适配层\n\n`@ai-zen/socket-pty/mcp` 提供一套 MCP server（stdio 传输），将终端操作暴露为 MCP 工具。\n\n### 在 MCP 宿主中注册\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"socket-pty\": {\n      \"command\": \"npx\",\n      \"args\": [\"@ai-zen/socket-pty\", \"mcp\"]\n    }\n  }\n}\n```\n\n### 工具\n\n| 工具 | 参数 | 返回 |\n|------|------|------|\n| `spawn` | `command`, `cwd?`, `cols?`, `rows?` | `address`（字符串） |\n\n`spawn` 的 `command` 与 CLI / 程序化接口一致，遵循[命令与平台](#命令与平台)：**Windows 上应传 `powershell.exe` / `cmd.exe`**（带 `.exe` 后缀），传 `powershell` / `bash` 会启动失败。\n| `read` | `address`, `top?`/`bottom?`/`start?`+`end?` | 屏幕文本 |\n| `write` | `address`, `action` | 写入成功（含字节数） |\n| `wait` | `address`, `match?`, `timeout?` | 匹配结果 + 屏幕 |\n| `resize` | `address`, `cols`, `rows` | 成功/失败 |\n| `status` | `address` | `{ running, exitCode, pid }` |\n| `kill` | `address` | 成功/失败 |\n\n### `spawn` 的地址来源\n\n`spawn` 由 MCP 管理器自分配本机临时端点，并在工具返回中给出该 `address`：\n\n- **Unix**：`unix:/tmp/pty-mcp-<pid>-<时间戳>-<随机>.sock`\n- **Windows**：`tcp:127.0.0.1:<port>`（先探测分配一个空闲端口）\n\n该 `address` 由管理器分配并返回给调用方，作为后续工具的 `address` 参数使用。会话为孤儿进程，常驻直到被 `kill`。\n\n`spawn` 的自动分配仅存在于 MCP 内部——管理器既分配地址又持有使用，地址始终在其掌握中。对外提供端点（`serve` / `createSocketPty`）则必须显式定址。\n\n### 编程式引入\n\n```typescript\nimport { MCPManager, ConnectionPool } from \"@ai-zen/socket-pty/mcp\";\n```\n\n`MCPManager` 提供 `spawn` / `read` / `write` / `wait` / `resize` / `status` / `kill` 方法，经内部 `ConnectionPool`（按地址复用连接，JSON-RPC 2.0）分发到对应 serve 进程。\n\n---\n\n## 会话生命周期\n\n- **常驻**：`serve`/`spawn` 启动的进程独立常驻，客户端的断开重连不影响其存活与当前屏幕。\n- **连接**：一个端点可被多个客户端连接；每个请求独立带 `id`，按 `id` 对应响应。\n- **终止**：`pty/kill` 终止会话并关闭端点；之后端点不可再连接。\n- **清理**：Unix socket 端点关闭时，对应 socket 文件被移除。\n\n---\n\n## 已知限制\n\n- **仅当前屏幕，无历史日志**：`pty/read` 返回当前画面（`rows` 行），不提供滚动出屏外的历史回读。\n- **依赖 node-pty（必需）**：无降级后端。Linux 需能编译 node-pty（见[依赖](#依赖)）。\n- **Windows 命令需带 `.exe` 后缀**：node-pty 在 Windows 上不对裸命令名做 PATH 解析，传 `powershell` / `bash` 会 `File not found`；应传 `powershell.exe` / `cmd.exe` 或完整路径（见[命令与平台](#命令与平台)）。\n- **不支持通知**：所有请求必须带 `id`。\n- **对外端点必须显式定址**：`serve`/`createSocketPty` 不接受自动分配；MCP 的 `spawn` 为特例，地址由其自分配并返回。\n\n---\n\n## 许可证\n\nMIT\n","readmeFilename":"README.md"}