# @beihaizb/dsh-notebook [![npm version](https://img.shields.io/npm/v/@beihaizb/dsh-notebook.svg)](https://www.npmjs.com/package/@beihaizb/dsh-notebook) [![npm downloads](https://img.shields.io/npm/dm/@beihaizb/dsh-notebook.svg)](https://www.npmjs.com/package/@beihaizb/dsh-notebook) [![license](https://img.shields.io/npm/l/@beihaizb/dsh-notebook.svg)](https://github.com/beihzb/dsh-notebook/blob/main/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/beihzb/dsh-notebook.svg?style=social)](https://github.com/beihzb/dsh-notebook) [English](README.md) | [中文](README.zh-CN.md) **面向 DeepSeek Harness 的、由 Agent 操控的持久化 Jupyter 计算工作空间。** 一个真正的 `ipykernel` runtime,DSH Agent 可以读取、编辑、执行并检查它的状态——把 notebook 从静态产物变成 Agent 可以操作的对象。 这不是又一个 Notebook 前端。内核是**真正持久的**(变量跨 cell 保持)、notebook 可**保存/重新加载**,而且 Agent 拥有读取、修改、运行、推理 cell 及其输出的**工具**。这给了 Agent 一个真实的 **Agent ↔ 内核 ↔ 产物** 闭环。 ## Agent 工作流 这个插件的意义在于:DSH Agent 不在运行一次性脚本,而是在驱动一个活跃的计算会话: > 用户:*"把 Harmony 换成 scVI。"* > → Agent 找到相关 cell → 读取源码和上下文 → 修改 cell → 执行 → 检查 stdout / traceback → 必要时继续迭代。 因为内核是持久的,Agent 操作的是有状态 runtime(一个 `Anndata`、一个 GPU 模型、已加载的数据),而不只是拼接代码字符串。 ## 演示 / 截图 - 实时 **tqdm 进度条**、内联图可点击放大。 - **可点击 traceback**(`Cell In[N]`)跳转到对应 cell。 - 逐 cell **「交给 AI」** 修改意见:输入需求,Agent 修改并重跑该 cell。 - VS Code 对齐的 cell 行为(排队 / 运行中 / 执行语义)。 ## 功能(已实现) - **真正的持久内核**:`ipykernel` + `jupyter_client` sidecar,变量跨 cell 保持。**R 内核**(`IRkernel`)同样支持,在同一个下拉框里选。 - **Agent 工具**:读取、编辑、运行、检查 cell(`nb_get` / `nb_edit_cell` / `nb_run_cell` …),结构化访问输出与 traceback。 - **Agent 运行时自省**:`nb_context`、`nb_list_vars`、`nb_inspect_object` 暴露活内核变量和压缩后的 notebook 状态;pandas `DataFrame` 会返回 shape / dtype / 缺失值 / head / summary 等元信息。 - **安全 AI 修改闭环**:更完整的逐 cell 版本快照,配合 `nb_cell_history`、`nb_revert_cell`、`nb_error_context`、`nb_edit_and_run_cell` 支持可审计的修错并重跑流程。 - **VS Code 对齐的 cell UI**:圆形运行控件、`Queued` / `Executing` 状态栏、执行序号。 - **VS Code 对齐的执行语义**:重启保留已完成输出、中断只停当前 cell、Run All 遇错停止、Clear Outputs / Restart & Clear。 - **tqdm 进度条**、**长输出折叠**、**多图网格**。 - **错误定位**(点击 traceback 跳到对应 cell)。 - **更稳的前端**:加了 React 错误边界,输出渲染异常时不再整块空白;修复了一个 hooks 顺序 bug 导致的界面偶发消失。 - **更快的实时更新**:运行中进度节流并只发尾部输出、状态轮询降频、高频更新不再重复带完整内核列表。 - **跟随会话的工作目录**:notebook 默认 cwd / workspace 跟随当前 DSH 会话;手动“设置工作目录”与打开 notebook 保存的 cwd 优先级更高。 - **会话隔离**:每个 DSH 会话拥有独立的 notebook、内核和工作目录,无跨会话状态泄漏。多会话可同时运行内核。 - **内核管理器**:工具栏弹窗列出所有会话的活跃内核(会话 ID、内核名、状态),可直接关闭任一会话的内核。 - **内核活跃指示**:状态栏显示当前会话内核是否活跃,多会话同时开内核时提醒注意内存/资源。 - **孤儿进程自动清理**:dsh web 被强杀时,Python sidecar 检测到父进程死亡后自动关闭 ipykernel,不留孤儿进程(跨平台:POSIX 用 getppid,Windows 用 OpenProcess)。 - **中英双语界面**:整个界面默认英文,工具栏一键切换中文,选择持久化保存。 - **大小核 CPU 提示(仅 Windows)**:在混合架构(P+E 核)机器上,插件读取每个物理核的 `EfficiencyClass`,算出 P 核亲和性掩码,并给出**针对本会话内核**的一键复制片段——面板里写明是哪个内核,片段按内核语言切换:Python 给 `psutil` 脚本;R 给 `system2("powershell", …)` 包装,目标是自己 `Sys.getpid()`(也就是真正干活的 `Rterm.exe`,`R.exe` 只是启动器),因此不会碰到别的会话。**只在 Windows 做**是刻意的:macOS 没有按进程设亲和性的 API,Linux 上不额外装工具能读到的拓扑信号不可靠。工具栏的「⚡ 大小核」按钮可随时重新打开(只关闭"自动弹出")。 - **Jedi 内核补全**(`df.` / `plt.` / 变量名;Tab 接受;悬停看 docstring)。 - **每 cell AI 修改意见**(版本栈在 `cell.metadata.dsh`)。 - **标准 `.ipynb`** 保存 / 读取,自动保存 + 未保存提醒;不需要 `nbformat`,未知字段也不会在往返中丢失。 - **内核选择**:conda 环境 **和 R 内核**都能选;缺 `ipykernel` / `jupyter_client` / `IRkernel` 时给友好安装提示。 - **兼容 scoped 安装**:bundle 现在在 host 补丁(`cordis.patch.yml`)和 client 加载器里都注册为 `@beihaizb/dsh-notebook`,`dsh plugin add @beihaizb/dsh-notebook` 可直接加载(此前补丁名不带 scope,无法对上 npm 的 scoped 包名)。 - **「交给 AI」更可靠**:修复了一个回归(局部变量遮蔽 i18n 翻译函数),此前提交的意见会在发送前被静默丢弃——现在 cell 代码和你的请求一定能送达 agent。 - **编辑器跟随主题**:CodeMirror 编辑器跟随 DSH 深浅主题——深色用 oneDark,浅色用清爽的浅色方案(行号柔和灰),切换主题时实时重绘。 - **CPU 提示片段本地化**:一键复制的 `psutil` 片段注释跟随界面语言(中 / EN)。 - **结构化执行结果(v0.2.2)**:所有运行工具(`nb_run_cell`、`nb_edit_and_run_cell`、`nb_run_all`)现在返回统一执行信封——`status`、`execution_count`、`duration_ms`、`kernel_state`、合并的 `stdout`/`stderr`、`outputs_summary`、图片路径、`error` + `traceback_text`、`execution_index`——为 Agent 建立可靠执行循环。 - **长输出不撑爆上下文(v0.2.2)**:三层防线防止失控输出灌爆 Agent 上下文——driver 单流上限(500 KB)、信封内 6 KB 头尾摘要、完整合并文本落盘到 `_files/` 并返回路径供 Agent 按需读取。base64 图片只留在 UI,信封给文件路径。 - **逐次执行历史(v0.2.2)**:每次运行都记录到 `cell.metadata.dsh.executions`(每 cell 上限 50 条,随 notebook 持久化)——执行历史 UI 与重放式恢复的地基。 - **Run All 完成提示更诚实(v0.2.2)**:只有所有 cell 真正跑到终态(ok/error)才弹「全部运行完成」,不再在请求一返回就假完成。 - **排队即时反馈(v0.2.2)**:运行第二个 cell 时立刻显示 `Queued` 状态(HTTP 运行端点不再为 UI 阻塞等待),且运行中不会出现快照交换导致的编辑器闪空。 - **ANSI 流输出不再乱码(v0.2.4)**:含 ANSI 转义码但不含 `\r` 的内核流输出(彩色 Pdb 提示符、`colorama` 彩色打印)现在会在渲染前剥离转义码——此前虽然算出了清洗文本,渲染时却回退到原始 `o.text`,导致 `[32m`/`[0m` 垃圾原样显示在输出区。错误标题(`evalue`)同样处理。 - **长输出折叠真正可用(v0.3.0)**:v0.2.2 加上的折叠从未生效——唯一产生高度钳制的 `fold` 类,其判定却来自"已被钳制元素"的测量,溢出永远测不到,按钮也就不出现。现在改为测量内层未被钳制的元素,且折叠高度与判定共用同一个 `FOLD_PX` 常量;按钮还会显示隐藏规模(如「展开全部(3,000 行)」)。 - **超大输出只渲染头尾(v0.3.0)**:单个文本块超过 4,000 行或 200 KB 时,只渲染前 300 行 + 后 100 行,并明确标注「… 已省略 N 行」与一键「显示全部 N 行」——500 KB 的流不必为了折叠而全部驻留 DOM。 - **全新会话也能进 Notebook(v0.3.0)**:会话空白时 DSH 不渲染 `conversation.view` slot,所以此前必须先发一句话才能看到 Notebook 标签。现在空白会话的输入框上方会出现「📓 在本会话打开 Notebook」入口,点击即激活本视图并让常规视图标签栏出现(随后宿主会记住该会话的选择)。 - **切换视图不再丢滚动位置(v0.3.0)**:宿主同一时刻只渲染一个视图,切到「对话」会卸载 notebook 并把位置重置到顶部。现在滚动偏移与每个 cell 的展开/折叠选择都按会话记忆,切回来自动恢复。 - **支持 DSH 0.2.0(v0.3.0)**:`peerDependencies` 现在同时覆盖 `0.1.x-rc` 与 `0.2.0-rc` 两条线(`@deepseek-ai/dsh-tools: ^0.1.0-rc.6 || ^0.2.0-rc.1`),DSH 启动期的兼容性门禁不再拒绝本插件——旧的 `^0.1.0-rc.6` 上界是 `<0.2.0`,而只要有 `@deepseek-ai/dsh*` peer 不满足当前运行版本,门禁会**跳过整个 bundle**。client 清单里也改为指向真正提供 `slots` 服务的 client row `@deepseek-ai/dsh-client-ui-renderer`,不再引用根本不存在的 `@deepseek-ai/dsh-client-runtime`。 - **默认内核跟随本会话(v0.3.1)**:默认内核现在读取**当前会话**的 `dsh-envsel` 选择。此前它取的是存储文件里"最后一个"选择,于是 notebook 可能落到某个不相干会话挑的环境上;而被派出的子代理因为各自拥有独立会话,根本查不到任何选择。现在自身没有选择的会话会继承它向上最近的那个有选择的会话。 - **支持 R 内核(v0.4.0)**:内核下拉框现在会列出 R 内核,来源与 Jupyter 一致——用户数据目录里的 kernelspec、conda 环境自带的 `/share/jupyter/kernels`、以及只在 R library 里装了 `IRkernel` 的 conda 环境(自动合成)。conda 的 R 启动时会把该环境的 DLL 目录塞进 `PATH` 并设好 `R_HOME`,否则直接起二进制会以 `0xC0000135`(找不到 DLL)秒退;kernelspec 里 `argv[0]` 只写 `"R"` 的情况会解析成绝对路径,因此在未激活环境时也能用。`nb_list_vars` / `nb_inspect_object` 有对应的 R 实现(`ls`/`class`/`deparse`,`data.frame` 额外给行数/列数/列类型),保存时写入真实的 `kernelspec.language`(`r` 或 `python`)。 - **不再需要 `nbformat`(v0.4.0)**:`.ipynb` 的读写改用标准库完成。此前 sidecar 在打开/保存 notebook 时会 `import nbformat`,于是一个有 `ipykernel` + `jupyter_client` 却没有 `nbformat` 的 conda 环境会直接报 `Operation failed: No module named 'nbformat'`——而精简环境或纯 R 环境根本装不了它。现在写出的字节与 `nbformat.write()` 完全一致(缩进/排序/CRLF/拆行规则全部对齐),用 Jupyter 打开再保存不会在 git 里炸开 diff。 - **notebook 往返无损(v0.4.0)**:插件不认识的东西一律原样保留——markdown 的 `attachments`(粘贴的图片)、每个 output 的 `metadata`(widget 视图尺寸)、全局 `metadata.widgets` 状态、第三方自定义键,保存时不会被吞掉。`nbformat` **3** 的老 notebook(`worksheets`/`input`/`prompt_number`/`png` 别名)会在读取时升级到 v4,而不是被读成空 notebook、再在下次保存时把文件清空;4.0–4.4 的文件按 `nbformat.read` 的行为补齐 cell id。 - **大小核提示可以重新打开(v0.4.0)**:以前点「知道了,不再提示」后整个浏览器会话都没有入口。现在 CPU 探测结果会留在会话里,工具栏的「⚡ 大小核」按钮可重新打开同一个面板;被关掉的只是**自动弹出**。 - **cwd 同步不再杀内核(v0.4.1)**:新挂载的 notebook 视图会用「杀掉 sidecar + 重启内核」的方式同步会话 cwd——这会丢掉所有变量、打断正在运行的 cell,而且当会话状态是用过时的 `process.cwd()` 创建时会**无故触发**。现在新会话从 `SessionHeader.cwd` 初始化,cwd 同步与 `nb_set_cwd` 都改为**原地切换目录**(排在正在跑的 cell 之后,因此不会打断它)。致谢:[#6](https://github.com/beihzb/dsh-notebook/pull/6)。 - **`/nb` 路由加了鉴权与请求体上限(v0.4.1)**:notebook 的 HTTP 路由现在先向 `connection` 服务要一个拒绝判定——也就是平台挡在 `/api` 前面的同一套 Host/Origin + 登录 Cookie 围栏——同时请求体上限 32 MB。此前任意本地进程、或任意网站通过跨站表单 POST,都能驱动内核执行任意 Python。致谢:[#3](https://github.com/beihzb/dsh-notebook/pull/3)。 - **不再把 Promise 漏进工具输出(v0.4.1)**:正在运行的 run Promise 以前挂在 cell 对象上,于是 cell 处于排队/运行中时,每次快照(也就包括 `nb_get`、`nb_edit_cell`、`nb_run_cell`)都携带一个活的 Promise。现在任务放在旁挂 Map 里,快照保证可 JSON 序列化。致谢:[#5](https://github.com/beihzb/dsh-notebook/pull/5)。 - **服务器上不再误报大小核(v0.4.1)**:Linux 上"用 `cpu MHz` 最大值−最小值 > 300 判混合架构"的兜底逻辑,对任何支持调频的 EPYC/Xeon 都会误报——空闲核降频、忙碌核睿频,差值自然超过 300。现在只认显式的 per-core `core_type` 标记。致谢:[#4](https://github.com/beihzb/dsh-notebook/pull/4)。 ## Roadmap 方向是完整的 **Agent 计算工作空间**,而不是堆更多 Notebook UI: - **AnnData / 科学对象自省** —— 扩展 `nb_inspect_object("adata")`,返回 `n_obs`、layers、`obsm`、`obs` / `var` 列摘要。 - **执行历史 / diff UI** —— 不只在工具里记录,也在界面里展示可审计的 cell 版本(逐次执行记录已开始收集)。 - **上下文相关的 cell 选择** —— 分层 / 查询"哪个 cell 定义了某变量、哪些依赖它",而不是把整个 notebook 塞进上下文。 - **执行安全** —— 把操作分类为 只读 / 轻量 / 修改型 / 重 / 破坏性;破坏性或超长运行前要求确认。 - **Checkpoint / 回滚** —— 不只恢复代码,还能恢复 runtime 状态。 - **远程 / SLURM 内核** —— 内核跑在服务器 / 作业节点上,Agent 通过同一接口驱动它。 ## 兼容性 支持的 DSH 运行时线:`0.1.x-rc` 与 `0.2.0-rc`,已在 `0.1.7-rc.2` 与 `0.2.0-rc.2` 上核对(插件声明 `@deepseek-ai/dsh-tools: ^0.1.0-rc.6 || ^0.2.0-rc.1`)。若插件的 `@deepseek-ai/dsh*` peer 区间不包含当前运行版本,DSH 会在启动时拒绝并**跳过整个 bundle**——在尚未验证的新版本线上,请用精确版本豁免(`dsh plugin allow-version` 或插件管理器),而不是强行加载。 ## 安装 ```bash dsh plugin --profile web add @beihaizb/dsh-notebook@0.4.1 ``` 然后重启 `dsh web`。会话顶部会出现 **Notebook** 标签。 > **版本锁定说明**:如果刚发布不久就安装,pnpm 的供应链策略(`minimumReleaseAge`)可能跳过刚发布的版本、自动装到旧版。遇到这种情况请像上面那样显式指定版本号(`@beihaizb/dsh-notebook@0.4.1`),或等发布超过策略窗口后用 `@beihaizb/dsh-notebook@latest`。 ### 内核选择 - **`dsh-envsel` 是可选依赖。** 装了环境选择器时,插件读取它的选择(`$DSH_HOME` 下的存储,即 `~/.dsh/envsel-state.json`),用你在**本会话**选的 conda 环境作为默认内核。自身没有选择的会话(例如被派出的子代理)会继承它向上最近的那个有选择的会话,因此被委托的 notebook 工作会落在委托它的那个会话所用的环境里。 ```bash dsh plugin --profile web add @beihaizb/dsh-envsel ``` (源码:[github.com/beihzb/dsh-envsel](https://github.com/beihzb/dsh-envsel)) - **没装 `dsh-envsel`** 时,回退到第一个发现的有 `ipykernel` 的 conda 环境。读取存储时会先校验其版本戳,因此未来换了格式的 `envsel` 状态会被忽略,而不是被误读。 - **随时可切换。** 用工具栏下拉框切换任意其他环境(Python 或 R)。 ### 内核依赖 | 内核 | 需要 | |---|---| | Python | `ipykernel` **和** `jupyter_client`——前者跑内核,后者跑与它通信的 sidecar | | R | 该 R 的 library 里有 `IRkernel`;不需要任何 Python 包(sidecar 借用某个 Python 环境,优先用 `dsh-envsel` 选的 python) | `nbformat` **不需要**——notebook 文件用标准库读写。 `jupyter_client` 与 `ipykernel` 是真正第三方的协议实现,缺了会给出精确的安装命令,而不是抛原始 `ModuleNotFoundError`;内核下拉框也会把这类环境标记为不可用,避免你选到一个起不来的内核。 **R 内核需要主动选择**:新建 notebook 不会悄悄启动 R。要跑 R notebook 就在下拉框里选 `R · ggtree`、`R · hdWGCNA` 等。下拉框里每个条目都带语言前缀——`py · scRNAv2`、`R · ggtree`——两者不会混淆;`nb_kernel_list` 也返回同样的 `language` 字段(`"python"` / `"r"`)。而一个只有 Python、但顺带装了 R 的 conda 环境(很常见)仍会被 `dsh-envsel` 写进运行时上下文,agent 因此知道两条调用路径。 ## 工具 `nb_new` / `nb_open` / `nb_save` / `nb_get` / `nb_context` / `nb_list` / `nb_list_vars` / `nb_inspect_object` / `nb_add_cell` / `nb_delete_cell` / `nb_move_cell` / `nb_edit_cell` / `nb_edit_and_run_cell` / `nb_cell_history` / `nb_revert_cell` / `nb_error_context` / `nb_run_cell` / `nb_run_all` / `nb_apply_suggestion` / `nb_kernel_restart` / `nb_kernel_restart_and_clear` / `nb_kernel_interrupt` / `nb_kernel_list` / `nb_kernel_select` / `nb_clear_outputs` / `nb_set_cwd` ## License MIT