{"_id":"@dshworks/dsh-crew","_rev":"4-711d8db08db2bdb46a9986310162d911","name":"@dshworks/dsh-crew","dist-tags":{"latest":"0.2.2"},"versions":{"0.1.0":{"name":"@dshworks/dsh-crew","version":"0.1.0","keywords":["deepseek","deepseek-harness","dsh","dsh-plugin","claude-code","codex","terminal","pty","xterm","split-view","multi-agent","agent-team"],"license":"MIT","_id":"@dshworks/dsh-crew@0.1.0","maintainers":[{"name":"lroolle","email":"reason-source6s@icloud.com"}],"homepage":"https://github.com/dshworks/dsh-crew#readme","bugs":{"url":"https://github.com/dshworks/dsh-crew/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-ui-conversation","@deepseek-ai/dsh-client-locale"],"platform":"web"}},"dist":{"shasum":"1cebb94517fb160432d4da11b995a442f67e89f1","tarball":"https://registry.npmjs.org/@dshworks/dsh-crew/-/dsh-crew-0.1.0.tgz","fileCount":20,"integrity":"sha512-x02rnKul+14wtqN9DOQeGvmweY1oPnv+kZSuqbWtevuwDOJX6o35vGAm/TgurmSa7rl0pdHa9cAuWiyS9g3mzA==","signatures":[{"sig":"MEUCIQCBNk3FtI8NcjVHKCPChDOPPn+Cg7Wge0/inV05gEgf/wIgdlb9xh/GMHPMd5w8O+gMFxQ7bjQ0K5Tb01Pq7hT2cLE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":472952},"main":"lib/index.js","type":"module","engines":{"node":">=20"},"exports":{".":"./lib/index.js","./client":"./dist/client.js","./package.json":"./package.json"},"gitHead":"f65e246ba544707187276cec446e2ebe5f07189a","scripts":{"test":"node scripts/build-client.mjs --check && vitest run","build":"node scripts/build-client.mjs"},"_npmUser":{"name":"lroolle","email":"reason-source6s@icloud.com"},"repository":{"url":"git+https://github.com/dshworks/dsh-crew.git","type":"git"},"_npmVersion":"10.9.8","description":"Run Claude Code and Codex as live terminal panes beside dsh: a split view in the web UI, buttons to seat them, and tools that let the dsh agent hand them work and read their screens.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"ws":"^8.21.0","@xterm/headless":"^6.0.0","@deepseek-ai/schemastery":"^3.18.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.28.0","node-pty":"^1.1.0","@xterm/xterm":"^6.0.0","@xterm/addon-fit":"^0.11.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.0-rc.6","@deepseek-ai/dsh-subprocess":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-crew_0.1.0_1786948967234_0.42272337227641543","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@dshworks/dsh-crew","version":"0.2.0","keywords":["deepseek","deepseek-harness","dsh","dsh-plugin","claude-code","codex","terminal","pty","xterm","split-view","multi-agent","agent-team"],"license":"MIT","_id":"@dshworks/dsh-crew@0.2.0","maintainers":[{"name":"lroolle","email":"reason-source6s@icloud.com"}],"homepage":"https://github.com/dshworks/dsh-crew#readme","bugs":{"url":"https://github.com/dshworks/dsh-crew/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-ui-conversation","@deepseek-ai/dsh-client-locale"],"platform":"web"}},"dist":{"shasum":"8b63e1f1b4f2ca8c2a6e5222fac65e33b6b4c852","tarball":"https://registry.npmjs.org/@dshworks/dsh-crew/-/dsh-crew-0.2.0.tgz","fileCount":20,"integrity":"sha512-Za31nv722QhbvpyL+HUEkzksrIn+QGCzC6bTCvv+RywEj+oaoeB5cKTW0i1r74F5OD+IeIZ2bYcyNoKh19pDZA==","signatures":[{"sig":"MEQCIDoFJ1Pta8gK/zsvj/L2qdu0iwF4Gkr7AHdbi0VcZzWqAiAmdU+Ks7mi3g+bSt7qfYVsVYqmGKXl2kaRVbWasVEYzA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":493823},"main":"lib/index.js","type":"module","engines":{"node":">=20"},"exports":{".":"./lib/index.js","./client":"./dist/client.js","./package.json":"./package.json"},"gitHead":"e88450f3f6023102ea2ed6d94fc32f107e589d57","scripts":{"test":"node scripts/build-client.mjs --check && vitest run","build":"node scripts/build-client.mjs"},"_npmUser":{"name":"lroolle","email":"reason-source6s@icloud.com"},"repository":{"url":"git+https://github.com/dshworks/dsh-crew.git","type":"git"},"_npmVersion":"10.9.8","description":"Run Claude Code and Codex as live terminal panes beside dsh: a split view in the web UI, buttons to seat them, and tools that let the dsh agent hand them work and read their screens.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"ws":"^8.21.0","@xterm/headless":"^6.0.0","@deepseek-ai/schemastery":"^3.18.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.28.0","node-pty":"^1.1.0","@xterm/xterm":"^6.0.0","@xterm/addon-fit":"^0.11.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.0-rc.6","@deepseek-ai/dsh-subprocess":"^0.1.0-rc.6"},"_npmOperationalInternal":{"tmp":"tmp/dsh-crew_0.2.0_1786954970009_0.9584561864914369","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@dshworks/dsh-crew","version":"0.2.1","keywords":["deepseek","deepseek-harness","dsh","dsh-plugin","claude-code","codex","terminal","pty","xterm","split-view","multi-agent","agent-team"],"license":"MIT","_id":"@dshworks/dsh-crew@0.2.1","maintainers":[{"name":"lroolle","email":"reason-source6s@icloud.com"}],"homepage":"https://dsh.works/dsh-crew/","bugs":{"url":"https://github.com/dshworks/dsh-crew/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-ui-conversation","@deepseek-ai/dsh-client-locale"],"platform":"web"}},"dist":{"shasum":"081ea54869e496b96185f94138be4a8dc9d7942d","tarball":"https://registry.npmjs.org/@dshworks/dsh-crew/-/dsh-crew-0.2.1.tgz","fileCount":21,"integrity":"sha512-r3oodKjKLtxSWhun1XL0mybo1c2ICTHYk4MwZqP1HfdOXQwyd4lGJT54lcfSlCme72x6hy9nOLIFSoImrdu99g==","signatures":[{"sig":"MEUCIDjpsIhjQLw/ayZQxD+0AKbGpUL1srinnBOaxs3RQTzhAiEA0JSXCL3r8dBER7wHsLHR9JmwoTXfK1uM+A3lo6eWtlg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":500303},"main":"lib/index.js","type":"module","engines":{"node":">=20"},"exports":{".":"./lib/index.js","./client":"./dist/client.js","./package.json":"./package.json"},"gitHead":"0c09888e2e7b1e53d845a8c349addc99978152ab","scripts":{"test":"node scripts/build-client.mjs --check && vitest run","build":"node scripts/build-client.mjs"},"_npmUser":{"name":"lroolle","email":"reason-source6s@icloud.com"},"repository":{"url":"git+https://github.com/dshworks/dsh-crew.git","type":"git"},"_npmVersion":"10.9.8","description":"Run Claude Code and Codex as live terminal panes beside dsh: a split view in the web UI, buttons to seat them, and tools that let the dsh agent hand them work and read their screens.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"ws":"^8.21.0","@xterm/headless":"^6.0.0","@deepseek-ai/schemastery":"^3.18.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.28.0","node-pty":"^1.1.0","@xterm/xterm":"^6.0.0","@xterm/addon-fit":"^0.11.0"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-rc.1","@deepseek-ai/dsh-subprocess":"^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-rc.1"},"_npmOperationalInternal":{"tmp":"tmp/dsh-crew_0.2.1_1788501364212_0.6752696371062106","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"_id":"@dshworks/dsh-crew@0.2.2","dsh":{"bundle":{"patch":"./cordis.patch.yml"},"client":{"inject":["@deepseek-ai/dsh-client-runtime","@deepseek-ai/dsh-client-ui-conversation","@deepseek-ai/dsh-client-locale"],"platform":"web"}},"bugs":{"url":"https://github.com/dshworks/dsh-crew/issues"},"dist":{"shasum":"94191623b8907ea73832746dcd53e761a67e5e01","tarball":"https://registry.npmjs.org/@dshworks/dsh-crew/-/dsh-crew-0.2.2.tgz","fileCount":21,"integrity":"sha512-3sB5BLet0RSCwoZBB/F83VVUejART+DLV0/BSI1sr4FrlCmhGfd1r4/nHP/ITe67M8CDGDa0q9Ju9H9rGgxzdQ==","signatures":[{"sig":"MEYCIQD3ycHrghBd8Ig1AHeBbUzn4DIJrcQoFIMwISNZ2Hd4rwIhAP5zSjTs75F1BwzmXsABSgYD2anw96OYFDK6SHfPWq4B","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDMzBS4tcKfWN8xkesKrNbsuCU/OqgY80MxIP9YF1ObwwIga3a9beS9O5Nve/9QC+a42gbBgGGETckb5SZ/GzZgGEI="}],"unpackedSize":502978},"main":"lib/index.js","name":"@dshworks/dsh-crew","type":"module","engines":{"node":">=20"},"exports":{".":"./lib/index.js","./client":"./dist/client.js","./package.json":"./package.json"},"gitHead":"288f01f229b5d1616cfe4cea6dea49e4d3d96811","license":"MIT","scripts":{"test":"node scripts/build-client.mjs --check && vitest run","build":"node scripts/build-client.mjs"},"version":"0.2.2","_npmUser":{"name":"lroolle","email":"reason-source6s@icloud.com"},"homepage":"https://dsh.works/dsh-crew/","keywords":["deepseek","deepseek-harness","dsh","dsh-plugin","claude-code","codex","terminal","pty","xterm","split-view","multi-agent","agent-team"],"repository":{"url":"git+https://github.com/dshworks/dsh-crew.git","type":"git"},"_npmVersion":"10.9.8","description":"Run Claude Code and Codex as live terminal panes beside dsh: a split view in the web UI, buttons to seat them, and tools that let the dsh agent hand them work and read their screens.","directories":{},"maintainers":[{"name":"lroolle","email":"reason-source6s@icloud.com"}],"_nodeVersion":"22.23.2","dependencies":{"ws":"^8.21.0","@xterm/headless":"^6.0.0","@deepseek-ai/schemastery":"^3.18.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.28.0","node-pty":"^1.1.0","@xterm/xterm":"^6.0.0","@xterm/addon-fit":"^0.11.0","@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-rc.1 || ^0.1.5-rc.1","@deepseek-ai/dsh-subprocess":"^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-rc.1 || ^0.1.5-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-tools":"^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-rc.1 || ^0.1.5-rc.1","@deepseek-ai/dsh-subprocess":"^0.1.0-rc.6 || ^0.1.1-rc.1 || ^0.1.2-rc.1 || ^0.1.5-rc.1"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-crew_0.2.2_1789708014425_0.8620798096709743"}}},"time":{"created":"2026-08-17T06:42:46.374Z","modified":"2026-09-18T05:06:54.782Z","0.1.0":"2026-08-17T06:42:47.375Z","0.2.0":"2026-08-17T08:22:50.152Z","0.2.1":"2026-09-04T05:56:04.373Z","0.2.2":"2026-09-18T05:06:54.511Z"},"bugs":{"url":"https://github.com/dshworks/dsh-crew/issues"},"license":"MIT","homepage":"https://dsh.works/dsh-crew/","keywords":["deepseek","deepseek-harness","dsh","dsh-plugin","claude-code","codex","terminal","pty","xterm","split-view","multi-agent","agent-team"],"repository":{"url":"git+https://github.com/dshworks/dsh-crew.git","type":"git"},"description":"Run Claude Code and Codex as live terminal panes beside dsh: a split view in the web UI, buttons to seat them, and tools that let the dsh agent hand them work and read their screens.","maintainers":[{"name":"lroolle","email":"reason-source6s@icloud.com"}],"readme":"<table>\n<tr>\n<td width=\"40%\" valign=\"top\">\n\n# dsh-crew\n\n[English](README.md) | 中文\n\n### 把 Claude Code 和 Codex 作为实时终端开在 dsh 旁边。你看得见它们干活，也随时能接管键盘。\n\ndsh 本来就能把活派给它们 —— `subagent-claude-code` 和 `subagent-codex`\n会启动对应产品、交给它一个任务，然后把最后一句话带回来。它们刻意不做的，\n是让你**看见过程**：没有进度流，没有人介入的通道，面向模型的终端工具\n文档里明写着 \"no TUI\"。\n\n0.1.0-rc.8 把这两个 subagent 改成了按需安装的 Profile Bundle，给 Codex 加了\n具名实例，还加了非交互的权限模式 —— 这不是把差距补上了，而是把分工画得更清楚。\n看 rc.8 的 provider 说明：每次 query 依然 \"never waits for a user interface\"，\n`AskUserQuestion` 依然是关的，而且除 bypass 模式外，`canUseTool` 现在会直接\n**拒绝**任何当场需要人确认的请求。上游是在有意把无人值守的委派做得更好。\n这个插件负责另一半：你想亲眼看着它跑，并且能随时回答它。\n\n`dsh-crew` 补上缺的那一半。每个成员都拿到一个真实 PTY，在本会话的工作区里\n跑它自己的 CLI，并把字节流送到 Web UI 的一个面板里。中间那一栏的 agent\n负责让它们入座、给它们派活；而这一切的每一次击键你都看得见，\n任何时候都可以直接在面板里打字。\n\n[![site](https://img.shields.io/badge/site-dsh.works%2Fdsh--crew-00c2e9)](https://dsh.works/dsh-crew/)\n[![ci](https://github.com/dshworks/dsh-crew/actions/workflows/ci.yml/badge.svg)](https://github.com/dshworks/dsh-crew/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/@dshworks/dsh-crew?color=4D6BFE)](https://www.npmjs.com/package/@dshworks/dsh-crew)\n[![powered by dsh](https://img.shields.io/badge/powered__by-dsh-4D6BFE?logo=deepseek)](https://github.com/deepseek-ai/deepseek-harness)\n[![license: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\n</td>\n<td width=\"60%\" valign=\"top\">\n\n<img src=\"https://raw.githubusercontent.com/dshworks/dsh-crew/main/docs/crew-dark.png\" alt=\"dsh Web UI 中 Chat 和 Trajectory 旁边打开的团队标签页：入座栏里列着 Claude Code、Codex 和 dsh，一个面板里跑着真实的 Claude Code 终端界面，工作目录就是本会话的工作区\" width=\"100%\">\n\n</td>\n</tr>\n</table>\n\n## 安装\n\n```sh\ndsh plugin --profile web add @dshworks/dsh-crew\ndsh --profile web\n```\n\n`dsh plugin` 转发给 pnpm，所以 pnpm 需要在 PATH 上。下一个会话里，\n**团队**标签页就会出现在 Chat 和 Trajectory 旁边。\n\n不需要额外配置。入座栏会列出它能找到的所有成员，找不到的那些则被禁用 ——\n原因直接写在按钮上，所以缺失的 `codex` 显示为\n`codex is not on the host PATH`，而不是点下去一秒后才失败。\n\n## 两半\n\n**人的那一半**是分屏：一条入座栏，每个入座成员一个面板。面板是真终端 ——\n有颜色、有光标定位、跑的是产品自己的 TUI —— 因为这个插件搬运的是 CLI 的\n字节，而不是重新实现它的界面。你随时可以往里打字，用 Ctrl-C 中断它，\n或者用群发输入框把同一句话同时送进所有存活面板，\n让两个 agent 并排回答同一个问题。\n\n**agent 的那一半**是五个工具，中间栏的模型用它们来带队：\n\n```text\ncrew_list                              → 谁能入座，谁已经在场\ncrew_seat(agent: \"claude\")             → 开一个面板，返回它的 id 和第一屏\ncrew_send(pane: \"…\", message: \"…\")     → 打字、回车，然后等它安静下来\ncrew_send(…, run_in_background: true)  → 立刻返回一个 job id，答案随完成通知回来\ncrew_peek(pane: \"…\")                   → 此刻的屏幕，和人看到的一模一样\ncrew_dismiss(pane: \"…\")                → 结束这个进程\n```\n\n共用同一个界面正是重点：派活和观察落在同一个终端上 —— agent 的\n`crew_send` 和你的眼睛看的是同一块屏幕，而不是一个任务 API 加一份日志。\n\n用 `tools: false` 关掉工具，分屏照常工作，只是团队变成纯人工驱动。\n\n### 为什么 `crew_send` 要等\n\n编程 agent 的回答要几十秒才出来。发完就返回会逼模型轮询，\n每采样一次烧掉一个 turn。所以 `crew_send` 在面板**安静下来**时才返回 ——\n即渲染出的屏幕在一段静默窗口内不再变化。超时了它会明说，\n并让模型稍后 `crew_peek`；成员那边照样继续干。\n\n但只看「安静」在两个方向上都不够，而这三处修正都来自真实 CLI，\n不是来自测试用的那个 shell：\n\n- **回车和消息分两次写。** 两个产品都会把「一串字节以回车结尾」当成\n  **粘贴**，回车于是变成输入框里的换行 —— 所以 `message + \"\\r\"` 一次写完\n  只会把任务打进输入框，一个字都没发出去。此时面板反而静了下来，\n  只按静默判定的话，模型拿回的就是自己那句还没发出去的问题。\n- **没画出来的屏幕不算安静。** 还没渲染出第一帧的 CLI 安静得很，\n  所以入座要同时等到「有内容」和「静下来」，才算这个成员就位。\n- **第一屏可能是个对话框，而插件不会替你回答它。** 在还没被信任过的\n  目录里，两个产品打开的都是自己的信任提示，而不是输入框。工具会把这件事\n  说清楚，并把决定权交给调用方：先用 `crew_send` 回答对话框 ——\n  空消息就是按一下回车 —— 等输入框出来了再发任务。任务发进对话框，\n  就是被打进了对话框，而其中的数字还可能顺手选中一个选项：\n  「从 1 数到 12」这句话，就曾经选中过 *2. No, quit*。\n  替产品答信任提示，不是插件该做的决定。\n\n### 后台发送\n\n`crew_send` 带上 `run_in_background: true`，这份等待就从当前 turn\n挪到 harness 的 job 接缝上：\n\n```text\ncrew_send(pane, message, run_in_background: true)\n  → started crew job crew-1 — job_output to read, crew_peek to watch\n  …… 模型继续干别的；人继续看着那个面板 ……\n  → background job crew-1 (crew: Codex ← Reply with exactly …) finished\n  → job_output(crew-1) → 成员说了什么\n```\n\njob 归调用的那个 agent 所有，所以 `job_list`、`job_output` 沿用 harness\n自己的会话围栏，完成通知也会唤醒空闲的模型，而不是丢掉。`job_kill`\n会停掉这份等待、向面板前台发 SIGINT，并让面板**继续在座** ——\n派活被取消，不构成关掉一个有人正在看的终端的理由。这个 SIGINT\n是否也终止了成员当前那一轮，由产品自己决定（有的只认 Esc）；\n而面板还开着，正是这件事仍然可挽回的原因：人随时可以接过键盘。\n它需要 `ctx.jobs` 以及调用方 agent 能够到的 job controller；\n缺了就如实说明，而不是抛异常。`enableRunInBackground: false`\n可以把这个参数整个去掉。\n\n两条路径返回的都是**新增**的那些行，而不是整屏：屏幕会与打字前取下的\n标记做差，于是模型读到的是答案本身，不必在自己早已看过的横幅里再找一遍。\n原地重绘的 CLI 算不出可用的差集，那就照旧返回整屏 ——\n而 `screen` 字段无论如何都带着它。\n\n### 为什么宿主还要再跑一个终端模拟器\n\n原始终端字节适合发给浏览器，却完全不适合塞进上下文窗口：全屏 CLI\n会绝对定位光标并重绘，所以字节流里大部分是转义序列，同一段文字还出现好几遍。\n剥掉转义序列并不能解决问题 —— 那样得到的是按到达顺序叠在一起的碎片，\n不是屏幕。\n\n于是宿主用浏览器同款模拟器、以无头方式跑同一份字节。`crew_peek`\n返回的就是人正在看的那张字符网格。这也是本插件唯一需要依赖终端模拟器的原因。\n\n## 名册\n\n| id | 名称 | 命令 |\n|---|---|---|\n| `claude` | Claude Code | `claude` |\n| `codex` | Codex | `codex` |\n| `dsh` | dsh | `dsh` |\n\n每个都以**交互形态**启动 —— 不带 prompt 参数的裸命令，\n这才会让 CLI 进入它自己的终端界面，而不是一次性模式。\n\n名册条目是数据，不是代码。加第四个 agent 就是一行配置，\n永远不需要新包：\n\n```yaml\n# ~/.dsh/profiles/web/cordis.patch.yml\n- id: dsh-crew\n  config:\n    agents:\n      - id: aider\n        label: Aider\n        command: aider\n        accent: '#7c3aed'\n```\n\n`id` 与内置项相同的行会逐字段**覆盖**那个内置项，\n所以把 `claude` 指向一个包装脚本只需一行。`enabled: false` 则移除一个。\n\n## 配置\n\n| 键 | 默认值 | 含义 |\n|---|---|---|\n| `agents` | `[]` | 额外成员，或按 `id` 覆盖内置项 |\n| `trustedHosts` | `[]` | 除环回外还允许访问这两个路由的 authority，与部署已有的 `--trusted-host` 保持一致 |\n| `cols` / `rows` | `100` / `30` | 浏览器没量出尺寸时的面板几何 |\n| `scrollbackBytes` | `262144` | 每个面板保留的原始输出，用于重新加载后重绘 |\n| `maxPanesPerSession` | `6` | 单会话同时打开的面板上限 |\n| `graceMs` | `3000` | 关闭面板时从 SIGTERM 到 SIGKILL 的宽限 |\n| `tools` | `true` | 是否向 dsh agent 暴露这五个工具 |\n| `enableRunInBackground` | `true` | 是否提供 `crew_send` 的 `run_in_background`，需要 harness 的 job 接缝 |\n\n## 安全\n\n开一个面板就是在运维者的机器上起一个进程，\n所以本插件挂的这两个路由，标准比只读路由更严。\n\n- **工作目录绝不由浏览器决定。** 它来自请求指名的那个 dsh 会话，\n  与 harness 自带的 subagent provider 取法完全一致。会话解析不出来时\n  **直接拒绝**，而不是回退 —— 回退到服务端 cwd 意味着仅凭一个不认识的 id，\n  就把 dsh 启动目录的写权限交给了一个编程 agent。\n- **两个路由都有请求围栏**：`Host` authority（环回，或 `trustedHosts`\n  中声明的项）加 `Sec-Fetch-Site`/`Origin`。格式不对的 `trustedHosts`\n  条目会让**加载失败**，而不是留到请求时才报错。\n- **控制路由要求 `application/json`。** 这是实打实的控制项：\n  跨站\"简单请求\"正是浏览器不带 CORS 预检就发出的那种，而它设不了这个媒体类型，\n  所以恶意页面无法盲打到一个有副作用的操作上。\n- **WebSocket 需要一次性 token**，由通过围栏的控制路由签发，30 秒内有效 ——\n  这才证明了是一个通过围栏的调用方要的这条流。\n- **工具调用只能碰本会话自己的面板。**\n- **这里不写会话日志。** 原始终端字节不是对话状态。\n\n围栏回答的是\"这个请求是不是来自本机的 dsh UI\"。它不是认证，\n网络可达性依旧由 webserver 的 bind 策略决定 ——\n如果你把 dsh 暴露到 localhost 之外，那才是真正要紧的决定。\n详见 [SECURITY.md](SECURITY.md)。\n\n## 做的时候踩到的几件事\n\n- **不带原生依赖。** PTY 来自 harness 的 subprocess 接缝\n  （`ctx.subprocess.spawnTerminal`），因此本包继承了它的凭据擦除和进程树拆除，\n  自己不带任何编译扩展。`node-pty` 只是 devDependency，用来对着真 PTY 跑测试。\n- **`TERM` 由面板自己声明，不继承。** harness 服务通常从非交互 shell 启动，\n  于是环境里的 `TERM` 是 `dumb`；而编程 CLI 读到 `dumb`\n  会正确地判断自己不在终端上，从而关掉颜色和光标定位 ——\n  那恰恰是面板存在的意义所在。面板声明 `xterm-256color` / `truecolor`，\n  这也确实就是浏览器那一端的真实情况。\n- **`dist/client.js` 是提交进仓库的。** 安装这个包不该需要构建步骤。\n  `npm test` 会先跑 `build-client --check`，\n  所以过期的 bundle 会在 CI 上失败，而不是发到 npm 上去。\n\n## 开发\n\n```sh\npnpm install\npnpm test                   # 33 个测试，跑在真 PTY、真 socket 和真围栏上\nCREW_REAL_CLI=1 pnpm test   # 再加 4 个：真的把 claude 和 codex 请进来\n```\n\n那个可选套件是让「写到线上的字节」保持诚实的地方：它在一个临时工作目录里\n请每个产品入座，回答它开机时弹出的对话框，再让它回答一条两行的消息 ——\n前台和后台各一次。它需要凭据、要花模型 token，所以默认不跑。\n\n见 [CONTRIBUTING.md](CONTRIBUTING.md)。\n\n## 许可\n\nMIT —— 见 [LICENSE](LICENSE)。\n","readmeFilename":"README.zh.md"}