[Epic] 统一输出读取:inline 使用 cwd/.j-cli 存储与清理,notebook 按需直接返回富输出
状态:设计草稿,待维护者审阅;尚未创建子 issue、修改代码或运行验收测试。
代码基线:TTTPOB/jcli @ a93c74a(0.8.1)。
以下命令名、字段名及默认参数是本 issue 的建议契约,不表示现有 CLI 已支持。
1. 背景与目标
目前 process_outputs() 遇到 PNG/JPEG 会调用 save_base64_image(),通过 tempfile.mkstemp() 导出图片,然后返回 type/path/mime。这个机制支持 inline 执行后按路径看图,但对已经保存到 notebook 的图片制造了额外副本,并依赖临时目录。源码:executor.py
文件执行还会先调用 process_outputs(),再将 raw_outputs 写回 notebook;展示所需的临时图片写入失败,因而可能阻止原始输出保存。应解除输出持久化与 agent 展示之间的耦合。源码:file_execution.py
本次不是取消 inline 富输出,而是在保留其能力的基础上,统一输出提取、按需查看和本地结果清理。
已确定的设计边界
| 项目 |
决定 |
| Inline 的结果位置 |
当前调用有效 cwd 下的 .j-cli/outputs/;四个 agent 行为一致 |
| DSH inline |
不使用 DSH 原生 attachment store 做执行结果保存;与其他 agent 一样写工作区 |
| Notebook 执行 |
原始输出保存到 .ipynb,不重复导出到 .j-cli 或 /tmp |
| 默认执行结果 |
状态、有限文本和输出定位;不自动返回 image block 或图片附件 |
| Notebook 查看 |
增加读取工具;一次调用直接返回选中的文本、HTML 或图片,不要求中间导出文件 |
| 文本与 HTML |
对所选择的表示做原文透传,不摘要化、不擅自改成其他 MIME |
| 图片 |
只有显式查看时,才作为宿主支持的图片内容返回 |
| SVG |
首版能发现并读取原始 XML;可选依赖的光栅化放入独立 backlog |
| 清理职责 |
jcli 负责自己的 .j-cli/outputs;不清理任何 agent 的 storage、spill 或历史 |
| 依赖与复杂度 |
复用 Python 格式读取核心;不增加数据库、全局结果服务或清理 daemon |
9. 派发顺序与首版完成标准
先完成 S1 的协议与 fixtures 定稿。随后 S2、S4、S5、S6 可以并行;S3 接在 S2 后,S7/S8 接在 S6 后;S9 汇总集成。S10 独立排期,不阻塞首版。
首版完成要求是:inline 能力不退化、结果只落当前工作区、时间/数量清理可控、默认不看图、四个 agent 均能一步读取 notebook 指定输出,文本/HTML 不被无声改写。不是“CLI 成功导出了一张图片”就算完成整个 epic。
本文之外暂不扩展到宿主存储维护、Markdown attachments、完整 widget 状态还原、live kernel output 回溯或跨机器结果同步。
# [Epic] 统一输出读取:inline 使用 cwd/.j-cli 存储与清理,notebook 按需直接返回富输出
状态:设计草稿,待维护者审阅;尚未创建子 issue、修改代码或运行验收测试。
代码基线:TTTPOB/jcli @ a93c74a(0.8.1)。
以下命令名、字段名及默认参数是本 issue 的建议契约,不表示现有 CLI 已支持。
1. 背景与目标
目前 process_outputs() 遇到 PNG/JPEG 会调用 save_base64_image(),通过 tempfile.mkstemp() 导出图片,然后返回 type/path/mime。这个机制支持 inline 执行后按路径看图,但对已经保存到 notebook 的图片制造了额外副本,并依赖临时目录。[源码:executor.py](https://github.com/TTTPOB/jcli/blob/a93c74a5a1c5b0de136061a42d021ce06da204d7/jupyter_jcli/executor.py)
文件执行还会先调用 process_outputs(),再将 raw_outputs 写回 notebook;展示所需的临时图片写入失败,因而可能阻止原始输出保存。应解除输出持久化与 agent 展示之间的耦合。[源码:file_execution.py](https://github.com/TTTPOB/jcli/blob/a93c74a5a1c5b0de136061a42d021ce06da204d7/jupyter_jcli/file_execution.py)
本次不是取消 inline 富输出,而是在保留其能力的基础上,统一输出提取、按需查看和本地结果清理。
已确定的设计边界
| 项目 |
决定 |
| Inline 的结果位置 |
当前调用有效 cwd 下的 .j-cli/outputs/;四个 agent 行为一致 |
| DSH inline |
不使用 DSH 原生 attachment store 做执行结果保存;与其他 agent 一样写工作区 |
| Notebook 执行 |
原始输出保存到 .ipynb,不重复导出到 .j-cli 或 /tmp |
| 默认执行结果 |
状态、有限文本和输出定位;不自动返回 image block 或图片附件 |
| Notebook 查看 |
增加读取工具;一次调用直接返回选中的文本、HTML 或图片,不要求中间导出文件 |
| 文本与 HTML |
对所选择的表示做原文透传,不摘要化、不擅自改成其他 MIME |
| 图片 |
只有显式查看时,才作为宿主支持的图片内容返回 |
| SVG |
首版能发现并读取原始 XML;可选依赖的光栅化放入独立 backlog |
| 清理职责 |
jcli 负责自己的 .j-cli/outputs;不清理任何 agent 的 storage、spill 或历史 |
| 依赖与复杂度 |
复用 Python 格式读取核心;不增加数据库、全局结果服务或清理 daemon |
“没有中间文件”指 jcli 不再为 notebook 查看导出 PNG/HTML/JSON 供另一工具二次打开。宿主为呈现、请求或历史保存所做的内部附件管理不在此限制内。
2. 用户可见流程
2.1 Inline:先保存,之后决定是否看
j-cli exec SESSION --code '...'
↓
捕获原始输出
↓
需要持久化的结果写入 <effective-cwd>/.j-cli/outputs/<run-id>/
↓
返回小型目录、真实文件路径及 run 引用
↓
agent 自行决定是否读取
├── 不读取:图片内容不进入模型上下文
└── 读取:使用原生图片/文件工具,或 jcli 的输出读取命令
无需用户新增 notebook,普通 CLI 和现有 agent shell 调用也必须工作。原有 inline PNG/JPEG 的 type/path/mime 能力保留,path 指向新的工作区文件,不可被无声替换成原生文件工具无法打开的伪路径。
仅有短 stdout/stderr 的执行可以直接返回文本,不应仅为返回 print(1) 而强制创建缓存目录。存在需要保留的富输出或超过展示预算的文本时,保存一份可回读的完整输出记录。
2.2 Notebook:执行不看,显式查看一步返回
j-cli exec SESSION --file analysis.py --cell 4
↓
原始输出写回配对的 analysis.ipynb
↓
只返回执行摘要与输出位置
read_notebook_output(file_path, cell_index, output_index, ...)
↓
Python 从已保存的 notebook 提取指定输出
↓
文本/HTML 原文透传;图片直接作为多模态工具结果返回
禁止把 notebook 查看实现成“导出临时图片 → 返回路径 → 要求 agent 再调用 read_image”。内部适配器调用一次 Python CLI 不违背“一步查看”;这里要求的是一次模型工具调用即可取得内容。
3. 共享输出语义与 API
3.1 读取核心
增加共享 Python 输出模块,供 CLI、DSH、OpenCode 和只读 MCP adapter 复用。TypeScript/JavaScript 插件只处理参数、调用、权限与宿主结果转换,不直接解析 .ipynb。
支持 stream、error、display_data、execute_result;保留 output 顺序、MIME bundle、metadata 和可用的 execution count。同一 bundle 内的 PNG、JPEG、HTML、plain text 是同一个 output 的不同表示,不因支持格式不同而重新编号。
公开的 output_index 使用实际 0-based cell.outputs 下标,包括 stream 和 error;“第几张图片”可以作为未来便利选择器,但不作为通用结果的主标识。
建议 CLI:
# 小型目录,不返回图片数据或大段 HTML
j-cli -j notebook outputs analysis.py --cell 4
# 查看一个输出;不指定 MIME 时使用确定性的自动选择规则
j-cli -j notebook output analysis.py --cell 4 --output 1
# 明确选择 HTML,不得偷偷替换成 text/plain
j-cli -j notebook output analysis.py --cell 4 --output 2 --mime text/html
# 从 inline 执行记录读取;不传 --output 时返回目录
j-cli -j output show .j-cli/outputs/<run-id>/manifest.json --output 1
建议共用工具接口:
read_notebook_output({
file_path: string,
cell_index: number,
output_index?: number, // 省略:只返回目录;指定:读取该输出
mime_type?: string, // 指定时必须返回该表示,或给出明确错误
offset?: number, // 可选文本窗口;不适用于图片
limit?: number
})
cell_index 沿用传入文件的 0-based 物理 cell 下标。.ipynb 直接定位;.py 复用现有配对/对齐逻辑,转换到实际 notebook cell,并返回实际定位。无法可靠对齐时明确报错,不盲目将两个文件的同一下标视作同一 cell。读取不得触发 pair sync、baseline 写入或 notebook 执行。
输出目录应能列出所有 MIME,包括暂不支持呈现的类型。单 cell 无 outputs 时目录为空,不等同于 cell 不存在。Markdown cell attachments 按附件名寻址,首版不混入 code cell outputs。
3.2 传输 envelope
建议单个 MIME 表示使用下列逻辑结构;字段在核心子 issue 中一次定稿,所有 adapter 共用 fixtures:
{
"schema_version": 1,
"status": "ok",
"source": {
"kind": "notebook",
"path": "/work/analysis.ipynb",
"cell_index": 4,
"output_index": 1
},
"output_type": "display_data",
"available_mime_types": ["image/png", "text/plain"],
"selected": {
"mime_type": "image/png",
"encoding": "base64",
"bytes": 12345,
"data": "..."
},
"metadata": {}
}
图片使用 base64 运输;文本使用 UTF-8 字符串;JSON MIME 保留 JSON 值,不进行额外的双重字符串化。bytes 是所选表示解码/编码为原始内容后的字节数,不是整个 JSON 传输长度。stream 和 error 保留自身结构,不能以图片式 envelope 丢掉 stderr 名称或 traceback。
Base64 只存在于 CLI→adapter 的内部传输,不能复制进最终模型可见的文字、错误日志、额外上下文或 MCP structuredContent。图片本身使用正确的多模态内容通道。
cell_id 等已存在的稳定标识应一并返回;读取结果明确表示“文件中已保存的输出”,不是对当前源码最新执行状态的保证。首版不要求建立执行版本数据库。
3.3 MIME 选择与透传
| 类型 |
首版行为 |
| PNG/JPEG |
解码与格式校验后由 adapter 返回真实图片;至少保持现有两种格式支持 |
| WebP/GIF |
提取层可支持;呈现以宿主实际支持能力为准,并明确动画是否被降级 |
| text/plain、text/markdown、stream |
透传原文;provenance 单独说明 |
| text/html |
透传原始 HTML;宿主只有 text block 时返回 HTML 源文本,并说明 MIME |
application/json、+json |
保留结构;宿主需要文本时仅做可逆 JSON 序列化 |
| error |
保留 ename/evalue/traceback,不把执行错误冒充读取接口失败 |
| image/svg+xml |
先提供 XML 源文本;转图片由 SVG backlog 实现 |
| 其他 MIME |
明确列出并说明是否可提取/呈现,不静默跳过或假装为空 |
不指定 mime_type 时使用固定且有测试的选择顺序,建议“宿主可支持的 raster image → HTML → Markdown → plain text → JSON → SVG 源文本”;明确选择优先于自动规则,同一 output 默认只呈现一种表示。
“HTML 透传”不是执行 HTML/JavaScript,不要求浏览器、截图、联网或 DOM 清洗。应避免把不可信 HTML 当作宿主 UI 的可执行标记直接注入,但不得以安全为由偷偷改变返回的源字符串。
文本窗口/大小上限必须显式报告 truncated、返回范围和后续读取方式。不得用摘要代替原文,也不得将截断 JSON 当成完整成功结果。宿主自身的上下文预算可能进一步截断,adapter 不承诺无限量文本全都进入模型。
4. Inline 工作区存储
4.1 cwd 语义
保存根目录固定为 <effective-cwd>/.j-cli/outputs。这里的 cwd 是当前 agent 调用实际使用的工作目录:有明确 workdir 时跟随该调用,否则用当前 session cwd。普通 CLI 使用它自己的进程 cwd。
不使用安装插件的目录、宿主全局 process.cwd()、git 仓库根目录、notebook 所在目录或远程 kernel cwd 代替。不同 cwd 隔离;同一 cwd 的不同 agent 共用目录,通过唯一 run ID 防止冲突。
不回退到 /tmp、系统缓存、用户 home、DSH attachment store 或其他 agent 存储。工作区不允许写入时,应报告保存失败,不寻找绕过限制的写入位置。
4.2 布局与完整性
建议采用每次执行一个结果组:
<cwd>/.j-cli/
└── outputs/
├── <run-id>/
│ ├── manifest.json
│ ├── output-0001.png
│ └── output-0002.html
└── <another-run-id>/
├── manifest.json
└── output-0000.jpg
Manifest 记录 jcli 管理标记、schema version、run ID、创建/完成时间、执行状态、原始 output 下标、MIME 目录和本目录内的 payload 定位。小文本可直接放 manifest;二进制保存为真实文件,不在 manifest 再保留一份相同 base64。
多 MIME 的 payload 定位必须保留对应关系,不能只保留展示优先级最高的一种。保存的原始图片不能为满足某个模型的输入尺寸而被覆盖;缩放属于查看阶段的宿主呈现行为。
文件先完整写入,再发布可读 manifest。需要暂存时只使用 .j-cli/outputs 内部位置;发布前后不能暴露半成品为成功结果。读取、清理及相对路径解析必须拒绝目录穿越和越界软链接;manifest 中的路径不等于可信删除权限。
保留现有 inline PNG/JPEG 的真实路径返回。Notebook-backed 执行改为返回 notebook 定位;这一差异需要显式的 release/migration 说明,不能伪造一个图片文件 path。
4.3 权限和失败语义
已有结果读取是只读操作:不得创建 .j-cli、更新 last-access、创建写锁或触发清理。只读模式下,已有文件仍可按宿主实际读权限查看;创建/清理文件需要真实的写权限。
Adapter 启动 CLI 必须沿用当前调用的 cwd、取消信号和有效 sandbox/权限策略。尤其不能在模型工具受限时,改由未受限宿主文件 API 替 inline 落盘。
本 issue 保证 jcli 结果文件写入遵守权限,不声称本地文件沙盒约束远程 Jupyter kernel 的任意副作用。执行权限与结果存储权限应分别处理。
若 kernel 已执行而落盘失败,错误需要区分“代码是否执行”和“输出是否保存”,不得自动重跑。能读到的有限文本和错误诊断应保留;未成功发布的文件不得返回可用路径。普通短文本执行不因无需使用的输出目录不可写而提前失败。
Notebook 执行先保存 raw_outputs,再生成摘要。后续显示、图片解码或宿主附件保存失败不能反向阻止已经捕获的原始输出写回;写回失败也不能悄悄改走 inline 缓存,掩盖 notebook 未更新。
5. 自动与手动清理
5.1 建议默认值
以下是草稿默认值,可在审阅时调整;不是已经确认的产品参数。
| 参数 |
建议值 |
语义 |
max_age_days |
7 |
按已完成结果组的完成时间计算,不依赖 atime |
max_runs |
50 |
每个 cwd 最多保留 50 个已完成 run;不是 50 个物理文件,也不是每个 agent 各 50 份 |
| 自动触发 |
一次新结果组成功发布后 |
机会式执行,不启动定时线程或 daemon |
先清除过期的已完成结果组,再在剩余结果超过数量时从旧到新删除。一次 run 的 manifest 与 payload 整组处理,避免只剩 manifest 或丢掉其中某种 MIME。
当前刚发布的 run 和仍在写入的 run 不得被本次清理删除。单次产生超过 50 张图不属于“超过 50 个 run”,不能丢弃当前输出;可以提示改用 notebook,但不能将提示变成能力降级或硬拒绝。
无新写入时不会精确在第七天自动删除;下次可写操作或手动清理时执行。时间/数量是保留策略,不是磁盘字节硬配额,首版不引入容量回收器。
建议通过环境配置覆盖自动默认值(具体名称在实现中统一,例如 JCLI_OUTPUT_MAX_AGE_DAYS、JCLI_OUTPUT_MAX_RUNS)。两者要求正数;0/负数不得被解释成“立即删除全部”。无需为清理单独引入配置框架。
5.2 手动命令
# 只列出拟删除项、原因与统计,不改变文件
j-cli -j output clean --dry-run
# 执行相同策略;参数覆盖本次命令的默认值
j-cli -j output clean --max-age-days 7 --max-runs 50
清理范围只限当前 cwd 下由 jcli 管理的 output run。不得递归删除整个 .j-cli,不得删除未知文件、用户手工放入的资料、notebook、其他 cwd 或任何 agent 的存储。若某个 run 中出现清单外文件,跳过该组并报告,不通过整目录删除误伤新增资料。
共享 cwd 下,多进程写入/清理需有最小必要的协调机制,不能清掉另一个活跃写入者。对崩溃留下的未完成目录,只有能确认已不活跃且超过保留期时才清理;不确定就保留并报告。无需为此开发跨主机锁服务。
自动清理失败只追加有限告警,不把已经成功保存的执行结果改成失败;手动 clean 返回明确失败/部分完成统计。删除权限不足不得触发提权或目录回退。
结果被清理后,后续读取返回 OUTPUT_NOT_FOUND,可说明“可能已被清理”;不要为了判断过期而维护永久 tombstone 数据库,也不要伪造已知过期时间。文档说明 .j-cli 是临时结果区,重要结果应复制到普通文件或改用 notebook。
6. Notebook 读取插件与传输
四个 agent 都应提供一次调用查看指定 notebook output 的能力,不以“先导出图片再原生 Read”作为首版完成标准。
| Agent |
建议接入 |
| DSH |
扩展已有单文件 TS 插件,注册 read_notebook_output,复用 ctx.shell;查看时使用宿主图片结果能力 |
| OpenCode |
扩展已有 JS 插件,注册同名工具;文本用 output,图片用 attachments |
| Claude Code |
安装很薄的 notebook 只读 stdio MCP adapter;保留现有 hooks |
| Codex |
复用同一个只读 stdio MCP adapter;保留现有 hooks |
OpenCode 当前插件结果支持 output 和 attachments 分离,可直接使用原生结果契约。[源码:ToolResult](https://github.com/anomalyco/opencode/blob/e03db9bc6908f75c9334d8aa997deeaac81c0298/packages/plugin/src/tool.ts)
Claude Code 的插件可包含 MCP server;Codex 支持本地 stdio MCP 配置。因此两个客户端可以共用一份只读 Python adapter,而不需要另写 notebook 解析器或建立 HTTP 服务。[Claude 插件文档](https://code.claude.com/docs/en/plugins-reference);[[Codex MCP 文档](https://developers.openai.com/codex/mcp/)](https://developers.openai.com/codex/mcp/)
DSH 的具体要求
保持单文件部署和零 @deepseek-ai/* runtime import 的现有方向。注册和执行按当前真实接口校验参数及 canonical value;output.render() 返回 block 数组。只读图片工具的 saveImage() 是查看阶段的呈现桥接,不是 inline 执行结果的保存后端。
必须覆盖 Native/PTC。执行和输出目录中没有 image block;显式查看图片才产生。工具不自己注入 UserMessage,不为 PTC 调用 exec.deferContext()。宿主原生转运负责图片进入模型,不由叶子工具重复实现。
大结果运输
DSH 当前有插件调用专用的 stdoutMaxBytes;为读取设置单次、有限预算,并与 CLI 序列化响应上限匹配。[源码契约:shell.md](https://github.com/deepseek-ai/deepseek-harness/blob/0d1f50007f9bca3f52b06e1c3074fa14d5fb0720/docs/subsystems/shell.md)
先检查取消、超时、退出状态及 stdout 完整性,再解析 JSON。正常支持范围内的图片不应依赖 shell spill 传输。仍被截断时返回明确错误;若提供兼容恢复分支,只能通过可信后端读取已确认完整的 spill,不把任意远程路径当作宿主本地路径。
OpenCode 的私有 stdout pipe 和 MCP 的结果同样需要有界字节读取、取消处理及超限诊断。限制是保护传输,不是无声截断原图。错误消息不得带整段 base64。
安装与权限
现有 setup claude/codex/dsh/opencode 负责安装新的读取能力,保持已有 scope 语义、幂等更新和只删除 jcli 管理条目的原则。移除插件不删除 .j-cli 结果,也不触碰用户已有 MCP、hooks、模型/provider 配置。
MCP 的只读标注不代替文件访问控制。相对路径、客户端 cwd、允许读取的根目录和符号链接需有明确策略;不能错误沿用全局 MCP 进程 cwd 读取其他项目。可优先要求模型使用绝对 notebook 路径;缺少可靠 cwd 时显式拒绝模糊的相对路径。
记录实际验证过的最小宿主版本。版本或依赖不支持新工具时,提供清晰提示,不能让 notebook 读取能力的加载失败顺带禁用现有 guards。
7. 子 issue 拆分
下列编号是草稿内部编号,不是已创建的 GitHub issue 编号。
| 编号 |
建议标题 |
依赖 |
| S1 |
[Core] 共享输出提取协议与 notebook outputs/output CLI |
无 |
| S2 |
[Storage] Inline 输出写入 cwd/.j-cli,并解除 notebook 执行的临时文件依赖 |
S1 的协议定稿;可先并行实现存储内部 |
| S3 |
[Cleanup] 工作区输出按时间/结果组数量自动与手动清理 |
S2 |
| S4 |
[DSH] 单文件插件按需读取 notebook 富输出 |
S1 |
| S5 |
[OpenCode] 原生插件按需读取 notebook 富输出 |
S1 |
| S6 |
[MCP] 共享 notebook 只读 stdio adapter |
S1 |
| S7 |
[Claude Code] setup 集成 notebook 读取工具并保留现有 hooks |
S6 |
| S8 |
[Codex] setup 集成 notebook 读取工具并保留现有 hooks |
S6 |
| S9 |
[Integration] 四 agent 验收、迁移文档、skills 与打包检查 |
S2–S8 |
| S10 |
[Backlog] 可选依赖的 SVG 内存光栅化 |
S1;不阻塞首版 |
S1 — 共享输出提取协议与 notebook CLI
**范围:**新增共享模块(建议 jupyter_jcli/notebook_outputs.py 或清晰分层的 outputs/);扩展 commands/notebook.py;实现目录、单 output、指定 MIME、文本窗口、配对定位和结构化错误。协议 fixtures 先稳定,供后续 adapter 并行开发。
**验收:**PNG/JPEG、HTML、文本、JSON、stream、error 和多 MIME bundle 均能正确读取;无 Jupyter 服务也能读取已保存 notebook;读取前后 notebook、cwd 和配对文件无写入;歧义配对不读错 cell;未知 MIME 不改变 output 下标。
**不包括:**执行存储、清理、agent 注册、SVG rasterizer。优先扩展 tests/test_notebook_cmd.py 并新增纯读取单元测试。
S2 — Inline 工作区存储与执行流程改造
**范围:**新增本地 output store,改造 executor.py、commands/exec_cmd.py、file_execution.py,必要时调整 notebook_writer.py;增加 output show 命令与所需配置接入。提取/格式化成为无落盘副作用的逻辑;是否保存到 notebook 或本地 run,由执行编排显式决定。
**验收:**四个 agent 及普通 CLI 的 inline 图均保存到有效 cwd;无 notebook 的其他执行路径也不丢富输出;PNG/JPEG 保留真实路径契约;有 notebook 不额外导出副本;短文本只读可用;写入被拒绝不会绕过 sandbox;不因模型无图片能力而拒绝保存;错误区分执行与保存。
**不包括:**DSH saveFile、其他宿主 storage、只支持 notebook 的退化方案、自动看图。保留文件执行 JSONL 的逐 cell 事件与最终 summary 结构,新增字段有兼容性测试。
S3 — 时间/数量清理
**范围:**实现一套可复用的清理策略,由结果成功发布后和 output clean 调用;增加配置、dry-run、删除原因/统计、并发保护和失败诊断。
**验收:**用可控时钟验证 7 天/50 run 示例;按整个结果组回收;当前及活跃 run 保留;并发写入不会互删;未知文件、其他 cwd、软链接目标和 notebook 不受影响;只读/dry-run 无写入;自动清理失败不改变执行成功;清理后读取明确失败。
**不包括:**宿主垃圾回收、后台 daemon、按模型上下文占用决定删除、容量硬配额。
S4 — DSH notebook 读取工具
**范围:**扩展 jupyter_jcli/dsh_plugin.ts,注册工具、调用共享 CLI、处理大响应和宿主图片呈现。更新 tests/js/dsh_plugin.test.mjs 及适配器安装资源测试。
**验收:**一次工具调用取得指定图;文本/HTML 原文一致;Native/PTC 都能查看;目录及执行结果零 image block;不产生 jcli 中间图片;大于普通 shell capture 上限的有效 fixture 成功;超限、取消、权限拒绝不误报成功;缺少可选读取能力时现有 hooks 仍工作。
**不包括:**DSH inline 附件保存、宿主 GC、重复 PTC bridge 或用户消息注入。
S5 — OpenCode notebook 读取工具
**范围:**扩展 jupyter_jcli/opencode_plugin.js;复用私有子进程管道;文本/HTML 放 output,图片放 attachments;接入 cwd、权限、取消及字节预算。增加对应 JS adapter 测试。
**验收:**一次调用取得指定图;图片 base64 不出现在普通 output/metadata;HTML 不变成摘要或 [HTML output];大响应与取消有测试;不写 OpenCode tool-output 或其他私有缓存;现有 edit/exec guards 不退化。
**不包括:**OpenCode storage/GC、执行完成自动附图。
S6 — 共享只读 stdio MCP adapter
**范围:**增加仅暴露 notebook 输出目录/读取的轻量 Python adapter,直接复用 S1;text/HTML 使用文本内容,图片使用 image content;JSON 结构按客户端兼容方式返回;输出 schema 与内容不重复放二进制。
**验收:**无需 HTTP、tunnel 或临时图片文件;无需 Jupyter server;只读目录可正常读 notebook;参数错误、路径权限、MIME 不支持、取消与超限可诊断;正常日志不污染 stdio 协议;使用同一组跨 adapter fixtures。
**不包括:**kernel 执行、任意 shell、notebook 修改、完整 Jupyter MCP 平台。确定 MCP 依赖是基础或按需安装;不使用该能力时,不使现有 CLI 因缺依赖无法启动。
S7 — Claude Code 安装与验证
**范围:**把 S6 集成到 setup claude 的适当配置/插件安装形态;保留现有 project/local/user 范围和 hooks。工具说明引导精确 output 查看,而非必须整本 Read。
**验收:**一次工具调用返回 notebook 指定图片;HTML/文本原文通过;重复 setup 无重复条目;remove 只移除 jcli 管理条目且保留 .j-cli 数据;多个项目不串 cwd;inline 仍通过普通 CLI 在工作区保存。
**不包括:**Claude plugin data 存储、清理 Claude 历史、依靠整本 notebook Read 代替本次精确工具。
S8 — Codex 安装与验证
**范围:**把 S6 集成到 setup codex 的 MCP 配置;保留既有 hooks 和 feature 检查;保留用户 config.toml 中的其他设置及格式语义。
**验收:**一次 MCP 调用返回 notebook 图片,而不是要求 view_image(.ipynb);HTML/文本原文通过;scope、幂等和 remove 正确;明确相对路径/cwd 规则;inline 仍返回 .j-cli 的真实 PNG/JPEG 路径供原生 view_image 使用。
**不包括:**用 hook additionalContext 塞图片、Codex 私有 attachment/data 目录、自动开启更宽权限。
S9 — 集成、文档与迁移
**范围:**更新 README、skills/j-cli/SKILL.md 和必要设计文档;在现有 git setup 的托管 ignore 中纳入 **/.j-cli/,保留其他条目;未使用 git setup 的用户提供相同配置说明。普通输出执行不暗中改写项目根 .gitignore。
**验收:**覆盖第 8 节总体验收矩阵;Python/JS tests 与构建按现有工作流通过;wheel 包含全部 adapter 资源;记录宿主版本和依赖;文档说明 cwd、清理、数据非永久、HTML 透传、按需看图及 notebook-backed JSON 迁移。旧 /tmp/jcli_* 不自动扫描或删除。
**不包括:**未经验证就宣称四 agent 端到端通过、重做现有 release 自动化。版本与锁文件更新遵守仓库 AGENTS.md,由发布阶段统一处理。
S10 — SVG 内存光栅化(backlog)
**范围:**通过可选依赖将选中的 SVG 在内存中渲染为宿主支持的 raster image;CLI/adapter 复用单一 Python 渲染接口;在来源说明中保留 SVG→PNG 等转换事实。
**验收:**没有额外依赖时仍可列出/读取 SVG 原文;启用后一次查看调用得到图;不创建中间 SVG/PNG 文件;限制像素、内存、耗时;禁用脚本、外部网络及任意本地文件引用;失败不静默变成空白图片;原始 SVG 不被覆盖。
**不包括:**HTML 浏览器渲染、交互式 widget、强制在基础安装中引入完整浏览器或系统图形栈。
8. 总体验收矩阵
| 场景 |
必须满足的结果 |
| Inline 画 1 张图,agent 不看 |
真实文件位于 cwd/.j-cli;执行结果没有 image block/base64 |
| 同一会话之后查看 inline 图 |
无需重跑代码,无需临时 notebook,可使用返回路径 |
| 测试 fixture 一次生成多图 |
全部保存;执行摘要有界;不能因 50-run 保留数丢图 |
| 有 notebook 的执行 |
输出写回 notebook;不创建 jcli 图片副本 |
| 一次查看 notebook 的一个图 |
一次模型工具调用直接收到图;没有导出→二次读取 |
| 读取 text/html |
原始 HTML 正文透传,provenance 分离,不执行脚本 |
| 读取 JSON、stderr、error |
类型和内容保留,不错误降级为 keys/占位符 |
| PNG + JPEG 同一 bundle |
一个 output,按选中的表示返回,不重复附图 |
.py 与 notebook 漂移 |
可靠映射或明确报错,不能读错 cell |
| cwd 与 kernel cwd/安装目录不同 |
存储跟随实际 agent 调用 cwd |
| 同 cwd 两个 agent 并发执行 |
run 不碰撞,清理不删除活跃结果 |
| 只读下查看已有结果 |
不创建目录、不写 last-access 元数据、不清理 |
| 只读下试图保存新 inline 图 |
写入被拒绝,不回退到宿主存储;说明代码是否已执行 |
| 清理过期/超数量结果 |
按组清理,当前 run 保留,dry-run 可审阅 |
| Notebook 位于只读工作区 |
读取不要求 .j-cli 可写;宿主内部呈现按其能力处理 |
| 大图片/超长文本 |
有界传输;无残缺 JSON 解析成功;无 base64 日志污染 |
| DSH Native/PTC |
执行不看图,指定查看才得到图,不重复转运 |
| 没有 SVG 可选依赖 |
其他功能正常;SVG 原文可读,渲染请求明确提示 |
| setup/remove |
保留用户配置和结果数据,现有 guards 不回归 |
9. 派发顺序与首版完成标准
先完成 S1 的协议与 fixtures 定稿。随后 S2、S4、S5、S6 可以并行;S3 接在 S2 后,S7/S8 接在 S6 后;S9 汇总集成。S10 独立排期,不阻塞首版。
首版完成要求是:inline 能力不退化、结果只落当前工作区、时间/数量清理可控、默认不看图、四个 agent 均能一步读取 notebook 指定输出,文本/HTML 不被无声改写。不是“CLI 成功导出了一张图片”就算完成整个 epic。
本文之外暂不扩展到宿主存储维护、Markdown attachments、完整 widget 状态还原、live kernel output 回溯或跨机器结果同步。
[Epic] 统一输出读取:inline 使用 cwd/.j-cli 存储与清理,notebook 按需直接返回富输出
1. 背景与目标
目前
process_outputs()遇到 PNG/JPEG 会调用save_base64_image(),通过tempfile.mkstemp()导出图片,然后返回type/path/mime。这个机制支持 inline 执行后按路径看图,但对已经保存到 notebook 的图片制造了额外副本,并依赖临时目录。源码:executor.py文件执行还会先调用
process_outputs(),再将raw_outputs写回 notebook;展示所需的临时图片写入失败,因而可能阻止原始输出保存。应解除输出持久化与 agent 展示之间的耦合。源码:file_execution.py本次不是取消 inline 富输出,而是在保留其能力的基础上,统一输出提取、按需查看和本地结果清理。
已确定的设计边界
9. 派发顺序与首版完成标准
先完成 S1 的协议与 fixtures 定稿。随后 S2、S4、S5、S6 可以并行;S3 接在 S2 后,S7/S8 接在 S6 后;S9 汇总集成。S10 独立排期,不阻塞首版。
首版完成要求是:inline 能力不退化、结果只落当前工作区、时间/数量清理可控、默认不看图、四个 agent 均能一步读取 notebook 指定输出,文本/HTML 不被无声改写。不是“CLI 成功导出了一张图片”就算完成整个 epic。
本文之外暂不扩展到宿主存储维护、Markdown attachments、完整 widget 状态还原、live kernel output 回溯或跨机器结果同步。
# [Epic] 统一输出读取:inline 使用 cwd/.j-cli 存储与清理,notebook 按需直接返回富输出1. 背景与目标
目前
process_outputs()遇到 PNG/JPEG 会调用save_base64_image(),通过tempfile.mkstemp()导出图片,然后返回type/path/mime。这个机制支持 inline 执行后按路径看图,但对已经保存到 notebook 的图片制造了额外副本,并依赖临时目录。[源码:executor.py](https://github.com/TTTPOB/jcli/blob/a93c74a5a1c5b0de136061a42d021ce06da204d7/jupyter_jcli/executor.py)文件执行还会先调用
process_outputs(),再将raw_outputs写回 notebook;展示所需的临时图片写入失败,因而可能阻止原始输出保存。应解除输出持久化与 agent 展示之间的耦合。[源码:file_execution.py](https://github.com/TTTPOB/jcli/blob/a93c74a5a1c5b0de136061a42d021ce06da204d7/jupyter_jcli/file_execution.py)本次不是取消 inline 富输出,而是在保留其能力的基础上,统一输出提取、按需查看和本地结果清理。
已确定的设计边界
.j-cli/outputs/;四个 agent 行为一致.ipynb,不重复导出到.j-cli或/tmp.j-cli/outputs;不清理任何 agent 的 storage、spill 或历史“没有中间文件”指 jcli 不再为 notebook 查看导出 PNG/HTML/JSON 供另一工具二次打开。宿主为呈现、请求或历史保存所做的内部附件管理不在此限制内。
2. 用户可见流程
2.1 Inline:先保存,之后决定是否看
无需用户新增 notebook,普通 CLI 和现有 agent shell 调用也必须工作。原有 inline PNG/JPEG 的
type/path/mime能力保留,path指向新的工作区文件,不可被无声替换成原生文件工具无法打开的伪路径。仅有短 stdout/stderr 的执行可以直接返回文本,不应仅为返回
print(1)而强制创建缓存目录。存在需要保留的富输出或超过展示预算的文本时,保存一份可回读的完整输出记录。2.2 Notebook:执行不看,显式查看一步返回
禁止把 notebook 查看实现成“导出临时图片 → 返回路径 → 要求 agent 再调用 read_image”。内部适配器调用一次 Python CLI 不违背“一步查看”;这里要求的是一次模型工具调用即可取得内容。
3. 共享输出语义与 API
3.1 读取核心
增加共享 Python 输出模块,供 CLI、DSH、OpenCode 和只读 MCP adapter 复用。TypeScript/JavaScript 插件只处理参数、调用、权限与宿主结果转换,不直接解析
.ipynb。支持
stream、error、display_data、execute_result;保留 output 顺序、MIME bundle、metadata 和可用的 execution count。同一 bundle 内的 PNG、JPEG、HTML、plain text 是同一个 output 的不同表示,不因支持格式不同而重新编号。公开的
output_index使用实际 0-basedcell.outputs下标,包括 stream 和 error;“第几张图片”可以作为未来便利选择器,但不作为通用结果的主标识。建议 CLI:
建议共用工具接口:
cell_index沿用传入文件的 0-based 物理 cell 下标。.ipynb直接定位;.py复用现有配对/对齐逻辑,转换到实际 notebook cell,并返回实际定位。无法可靠对齐时明确报错,不盲目将两个文件的同一下标视作同一 cell。读取不得触发 pair sync、baseline 写入或 notebook 执行。输出目录应能列出所有 MIME,包括暂不支持呈现的类型。单 cell 无 outputs 时目录为空,不等同于 cell 不存在。Markdown cell attachments 按附件名寻址,首版不混入 code cell outputs。
3.2 传输 envelope
建议单个 MIME 表示使用下列逻辑结构;字段在核心子 issue 中一次定稿,所有 adapter 共用 fixtures:
{ "schema_version": 1, "status": "ok", "source": { "kind": "notebook", "path": "/work/analysis.ipynb", "cell_index": 4, "output_index": 1 }, "output_type": "display_data", "available_mime_types": ["image/png", "text/plain"], "selected": { "mime_type": "image/png", "encoding": "base64", "bytes": 12345, "data": "..." }, "metadata": {} }图片使用 base64 运输;文本使用 UTF-8 字符串;JSON MIME 保留 JSON 值,不进行额外的双重字符串化。
bytes是所选表示解码/编码为原始内容后的字节数,不是整个 JSON 传输长度。stream和error保留自身结构,不能以图片式 envelope 丢掉 stderr 名称或 traceback。Base64 只存在于 CLI→adapter 的内部传输,不能复制进最终模型可见的文字、错误日志、额外上下文或 MCP
structuredContent。图片本身使用正确的多模态内容通道。cell_id等已存在的稳定标识应一并返回;读取结果明确表示“文件中已保存的输出”,不是对当前源码最新执行状态的保证。首版不要求建立执行版本数据库。3.3 MIME 选择与透传
+json不指定
mime_type时使用固定且有测试的选择顺序,建议“宿主可支持的 raster image → HTML → Markdown → plain text → JSON → SVG 源文本”;明确选择优先于自动规则,同一 output 默认只呈现一种表示。“HTML 透传”不是执行 HTML/JavaScript,不要求浏览器、截图、联网或 DOM 清洗。应避免把不可信 HTML 当作宿主 UI 的可执行标记直接注入,但不得以安全为由偷偷改变返回的源字符串。
文本窗口/大小上限必须显式报告
truncated、返回范围和后续读取方式。不得用摘要代替原文,也不得将截断 JSON 当成完整成功结果。宿主自身的上下文预算可能进一步截断,adapter 不承诺无限量文本全都进入模型。4. Inline 工作区存储
4.1 cwd 语义
保存根目录固定为
<effective-cwd>/.j-cli/outputs。这里的 cwd 是当前 agent 调用实际使用的工作目录:有明确workdir时跟随该调用,否则用当前 session cwd。普通 CLI 使用它自己的进程 cwd。不使用安装插件的目录、宿主全局
process.cwd()、git 仓库根目录、notebook 所在目录或远程 kernel cwd 代替。不同 cwd 隔离;同一 cwd 的不同 agent 共用目录,通过唯一 run ID 防止冲突。不回退到
/tmp、系统缓存、用户 home、DSH attachment store 或其他 agent 存储。工作区不允许写入时,应报告保存失败,不寻找绕过限制的写入位置。4.2 布局与完整性
建议采用每次执行一个结果组:
Manifest 记录 jcli 管理标记、schema version、run ID、创建/完成时间、执行状态、原始 output 下标、MIME 目录和本目录内的 payload 定位。小文本可直接放 manifest;二进制保存为真实文件,不在 manifest 再保留一份相同 base64。
多 MIME 的 payload 定位必须保留对应关系,不能只保留展示优先级最高的一种。保存的原始图片不能为满足某个模型的输入尺寸而被覆盖;缩放属于查看阶段的宿主呈现行为。
文件先完整写入,再发布可读 manifest。需要暂存时只使用
.j-cli/outputs内部位置;发布前后不能暴露半成品为成功结果。读取、清理及相对路径解析必须拒绝目录穿越和越界软链接;manifest 中的路径不等于可信删除权限。保留现有 inline PNG/JPEG 的真实路径返回。Notebook-backed 执行改为返回 notebook 定位;这一差异需要显式的 release/migration 说明,不能伪造一个图片文件
path。4.3 权限和失败语义
已有结果读取是只读操作:不得创建
.j-cli、更新 last-access、创建写锁或触发清理。只读模式下,已有文件仍可按宿主实际读权限查看;创建/清理文件需要真实的写权限。Adapter 启动 CLI 必须沿用当前调用的 cwd、取消信号和有效 sandbox/权限策略。尤其不能在模型工具受限时,改由未受限宿主文件 API 替 inline 落盘。
本 issue 保证 jcli 结果文件写入遵守权限,不声称本地文件沙盒约束远程 Jupyter kernel 的任意副作用。执行权限与结果存储权限应分别处理。
若 kernel 已执行而落盘失败,错误需要区分“代码是否执行”和“输出是否保存”,不得自动重跑。能读到的有限文本和错误诊断应保留;未成功发布的文件不得返回可用路径。普通短文本执行不因无需使用的输出目录不可写而提前失败。
Notebook 执行先保存
raw_outputs,再生成摘要。后续显示、图片解码或宿主附件保存失败不能反向阻止已经捕获的原始输出写回;写回失败也不能悄悄改走 inline 缓存,掩盖 notebook 未更新。5. 自动与手动清理
5.1 建议默认值
以下是草稿默认值,可在审阅时调整;不是已经确认的产品参数。
max_age_daysmax_runs先清除过期的已完成结果组,再在剩余结果超过数量时从旧到新删除。一次 run 的 manifest 与 payload 整组处理,避免只剩 manifest 或丢掉其中某种 MIME。
当前刚发布的 run 和仍在写入的 run 不得被本次清理删除。单次产生超过 50 张图不属于“超过 50 个 run”,不能丢弃当前输出;可以提示改用 notebook,但不能将提示变成能力降级或硬拒绝。
无新写入时不会精确在第七天自动删除;下次可写操作或手动清理时执行。时间/数量是保留策略,不是磁盘字节硬配额,首版不引入容量回收器。
建议通过环境配置覆盖自动默认值(具体名称在实现中统一,例如
JCLI_OUTPUT_MAX_AGE_DAYS、JCLI_OUTPUT_MAX_RUNS)。两者要求正数;0/负数不得被解释成“立即删除全部”。无需为清理单独引入配置框架。5.2 手动命令
清理范围只限当前 cwd 下由 jcli 管理的 output run。不得递归删除整个
.j-cli,不得删除未知文件、用户手工放入的资料、notebook、其他 cwd 或任何 agent 的存储。若某个 run 中出现清单外文件,跳过该组并报告,不通过整目录删除误伤新增资料。共享 cwd 下,多进程写入/清理需有最小必要的协调机制,不能清掉另一个活跃写入者。对崩溃留下的未完成目录,只有能确认已不活跃且超过保留期时才清理;不确定就保留并报告。无需为此开发跨主机锁服务。
自动清理失败只追加有限告警,不把已经成功保存的执行结果改成失败;手动 clean 返回明确失败/部分完成统计。删除权限不足不得触发提权或目录回退。
结果被清理后,后续读取返回
OUTPUT_NOT_FOUND,可说明“可能已被清理”;不要为了判断过期而维护永久 tombstone 数据库,也不要伪造已知过期时间。文档说明.j-cli是临时结果区,重要结果应复制到普通文件或改用 notebook。6. Notebook 读取插件与传输
四个 agent 都应提供一次调用查看指定 notebook output 的能力,不以“先导出图片再原生 Read”作为首版完成标准。
read_notebook_output,复用ctx.shell;查看时使用宿主图片结果能力output,图片用attachmentsOpenCode 当前插件结果支持
output和attachments分离,可直接使用原生结果契约。[源码:ToolResult](https://github.com/anomalyco/opencode/blob/e03db9bc6908f75c9334d8aa997deeaac81c0298/packages/plugin/src/tool.ts)Claude Code 的插件可包含 MCP server;Codex 支持本地 stdio MCP 配置。因此两个客户端可以共用一份只读 Python adapter,而不需要另写 notebook 解析器或建立 HTTP 服务。[Claude 插件文档](https://code.claude.com/docs/en/plugins-reference);[[Codex MCP 文档](https://developers.openai.com/codex/mcp/)](https://developers.openai.com/codex/mcp/)
DSH 的具体要求
保持单文件部署和零
@deepseek-ai/*runtime import 的现有方向。注册和执行按当前真实接口校验参数及 canonical value;output.render()返回 block 数组。只读图片工具的saveImage()是查看阶段的呈现桥接,不是 inline 执行结果的保存后端。必须覆盖 Native/PTC。执行和输出目录中没有 image block;显式查看图片才产生。工具不自己注入 UserMessage,不为 PTC 调用
exec.deferContext()。宿主原生转运负责图片进入模型,不由叶子工具重复实现。大结果运输
DSH 当前有插件调用专用的
stdoutMaxBytes;为读取设置单次、有限预算,并与 CLI 序列化响应上限匹配。[源码契约:shell.md](https://github.com/deepseek-ai/deepseek-harness/blob/0d1f50007f9bca3f52b06e1c3074fa14d5fb0720/docs/subsystems/shell.md)先检查取消、超时、退出状态及 stdout 完整性,再解析 JSON。正常支持范围内的图片不应依赖 shell spill 传输。仍被截断时返回明确错误;若提供兼容恢复分支,只能通过可信后端读取已确认完整的 spill,不把任意远程路径当作宿主本地路径。
OpenCode 的私有 stdout pipe 和 MCP 的结果同样需要有界字节读取、取消处理及超限诊断。限制是保护传输,不是无声截断原图。错误消息不得带整段 base64。
安装与权限
现有
setup claude/codex/dsh/opencode负责安装新的读取能力,保持已有 scope 语义、幂等更新和只删除 jcli 管理条目的原则。移除插件不删除.j-cli结果,也不触碰用户已有 MCP、hooks、模型/provider 配置。MCP 的只读标注不代替文件访问控制。相对路径、客户端 cwd、允许读取的根目录和符号链接需有明确策略;不能错误沿用全局 MCP 进程 cwd 读取其他项目。可优先要求模型使用绝对 notebook 路径;缺少可靠 cwd 时显式拒绝模糊的相对路径。
记录实际验证过的最小宿主版本。版本或依赖不支持新工具时,提供清晰提示,不能让 notebook 读取能力的加载失败顺带禁用现有 guards。
7. 子 issue 拆分
下列编号是草稿内部编号,不是已创建的 GitHub issue 编号。
[Core] 共享输出提取协议与 notebook outputs/output CLI[Storage] Inline 输出写入 cwd/.j-cli,并解除 notebook 执行的临时文件依赖[Cleanup] 工作区输出按时间/结果组数量自动与手动清理[DSH] 单文件插件按需读取 notebook 富输出[OpenCode] 原生插件按需读取 notebook 富输出[MCP] 共享 notebook 只读 stdio adapter[Claude Code] setup 集成 notebook 读取工具并保留现有 hooks[Codex] setup 集成 notebook 读取工具并保留现有 hooks[Integration] 四 agent 验收、迁移文档、skills 与打包检查[Backlog] 可选依赖的 SVG 内存光栅化S1 — 共享输出提取协议与 notebook CLI
**范围:**新增共享模块(建议
jupyter_jcli/notebook_outputs.py或清晰分层的outputs/);扩展commands/notebook.py;实现目录、单 output、指定 MIME、文本窗口、配对定位和结构化错误。协议 fixtures 先稳定,供后续 adapter 并行开发。**验收:**PNG/JPEG、HTML、文本、JSON、stream、error 和多 MIME bundle 均能正确读取;无 Jupyter 服务也能读取已保存 notebook;读取前后 notebook、cwd 和配对文件无写入;歧义配对不读错 cell;未知 MIME 不改变 output 下标。
**不包括:**执行存储、清理、agent 注册、SVG rasterizer。优先扩展
tests/test_notebook_cmd.py并新增纯读取单元测试。S2 — Inline 工作区存储与执行流程改造
**范围:**新增本地 output store,改造
executor.py、commands/exec_cmd.py、file_execution.py,必要时调整notebook_writer.py;增加output show命令与所需配置接入。提取/格式化成为无落盘副作用的逻辑;是否保存到 notebook 或本地 run,由执行编排显式决定。**验收:**四个 agent 及普通 CLI 的 inline 图均保存到有效 cwd;无 notebook 的其他执行路径也不丢富输出;PNG/JPEG 保留真实路径契约;有 notebook 不额外导出副本;短文本只读可用;写入被拒绝不会绕过 sandbox;不因模型无图片能力而拒绝保存;错误区分执行与保存。
**不包括:**DSH saveFile、其他宿主 storage、只支持 notebook 的退化方案、自动看图。保留文件执行 JSONL 的逐 cell 事件与最终 summary 结构,新增字段有兼容性测试。
S3 — 时间/数量清理
**范围:**实现一套可复用的清理策略,由结果成功发布后和
output clean调用;增加配置、dry-run、删除原因/统计、并发保护和失败诊断。**验收:**用可控时钟验证 7 天/50 run 示例;按整个结果组回收;当前及活跃 run 保留;并发写入不会互删;未知文件、其他 cwd、软链接目标和 notebook 不受影响;只读/dry-run 无写入;自动清理失败不改变执行成功;清理后读取明确失败。
**不包括:**宿主垃圾回收、后台 daemon、按模型上下文占用决定删除、容量硬配额。
S4 — DSH notebook 读取工具
**范围:**扩展
jupyter_jcli/dsh_plugin.ts,注册工具、调用共享 CLI、处理大响应和宿主图片呈现。更新tests/js/dsh_plugin.test.mjs及适配器安装资源测试。**验收:**一次工具调用取得指定图;文本/HTML 原文一致;Native/PTC 都能查看;目录及执行结果零 image block;不产生 jcli 中间图片;大于普通 shell capture 上限的有效 fixture 成功;超限、取消、权限拒绝不误报成功;缺少可选读取能力时现有 hooks 仍工作。
**不包括:**DSH inline 附件保存、宿主 GC、重复 PTC bridge 或用户消息注入。
S5 — OpenCode notebook 读取工具
**范围:**扩展
jupyter_jcli/opencode_plugin.js;复用私有子进程管道;文本/HTML 放output,图片放attachments;接入 cwd、权限、取消及字节预算。增加对应 JS adapter 测试。**验收:**一次调用取得指定图;图片 base64 不出现在普通
output/metadata;HTML 不变成摘要或[HTML output];大响应与取消有测试;不写 OpenCodetool-output或其他私有缓存;现有 edit/exec guards 不退化。**不包括:**OpenCode storage/GC、执行完成自动附图。
S6 — 共享只读 stdio MCP adapter
**范围:**增加仅暴露 notebook 输出目录/读取的轻量 Python adapter,直接复用 S1;text/HTML 使用文本内容,图片使用 image content;JSON 结构按客户端兼容方式返回;输出 schema 与内容不重复放二进制。
**验收:**无需 HTTP、tunnel 或临时图片文件;无需 Jupyter server;只读目录可正常读 notebook;参数错误、路径权限、MIME 不支持、取消与超限可诊断;正常日志不污染 stdio 协议;使用同一组跨 adapter fixtures。
**不包括:**kernel 执行、任意 shell、notebook 修改、完整 Jupyter MCP 平台。确定 MCP 依赖是基础或按需安装;不使用该能力时,不使现有 CLI 因缺依赖无法启动。
S7 — Claude Code 安装与验证
**范围:**把 S6 集成到
setup claude的适当配置/插件安装形态;保留现有 project/local/user 范围和 hooks。工具说明引导精确 output 查看,而非必须整本 Read。**验收:**一次工具调用返回 notebook 指定图片;HTML/文本原文通过;重复 setup 无重复条目;remove 只移除 jcli 管理条目且保留
.j-cli数据;多个项目不串 cwd;inline 仍通过普通 CLI 在工作区保存。**不包括:**Claude plugin data 存储、清理 Claude 历史、依靠整本 notebook Read 代替本次精确工具。
S8 — Codex 安装与验证
**范围:**把 S6 集成到
setup codex的 MCP 配置;保留既有 hooks 和 feature 检查;保留用户 config.toml 中的其他设置及格式语义。**验收:**一次 MCP 调用返回 notebook 图片,而不是要求
view_image(.ipynb);HTML/文本原文通过;scope、幂等和 remove 正确;明确相对路径/cwd 规则;inline 仍返回.j-cli的真实 PNG/JPEG 路径供原生 view_image 使用。**不包括:**用 hook
additionalContext塞图片、Codex 私有 attachment/data 目录、自动开启更宽权限。S9 — 集成、文档与迁移
**范围:**更新 README、
skills/j-cli/SKILL.md和必要设计文档;在现有 git setup 的托管 ignore 中纳入**/.j-cli/,保留其他条目;未使用 git setup 的用户提供相同配置说明。普通输出执行不暗中改写项目根.gitignore。**验收:**覆盖第 8 节总体验收矩阵;Python/JS tests 与构建按现有工作流通过;wheel 包含全部 adapter 资源;记录宿主版本和依赖;文档说明 cwd、清理、数据非永久、HTML 透传、按需看图及 notebook-backed JSON 迁移。旧
/tmp/jcli_*不自动扫描或删除。**不包括:**未经验证就宣称四 agent 端到端通过、重做现有 release 自动化。版本与锁文件更新遵守仓库
AGENTS.md,由发布阶段统一处理。S10 — SVG 内存光栅化(backlog)
**范围:**通过可选依赖将选中的 SVG 在内存中渲染为宿主支持的 raster image;CLI/adapter 复用单一 Python 渲染接口;在来源说明中保留 SVG→PNG 等转换事实。
**验收:**没有额外依赖时仍可列出/读取 SVG 原文;启用后一次查看调用得到图;不创建中间 SVG/PNG 文件;限制像素、内存、耗时;禁用脚本、外部网络及任意本地文件引用;失败不静默变成空白图片;原始 SVG 不被覆盖。
**不包括:**HTML 浏览器渲染、交互式 widget、强制在基础安装中引入完整浏览器或系统图形栈。
8. 总体验收矩阵
.py与 notebook 漂移.j-cli可写;宿主内部呈现按其能力处理9. 派发顺序与首版完成标准
先完成 S1 的协议与 fixtures 定稿。随后 S2、S4、S5、S6 可以并行;S3 接在 S2 后,S7/S8 接在 S6 后;S9 汇总集成。S10 独立排期,不阻塞首版。
首版完成要求是:inline 能力不退化、结果只落当前工作区、时间/数量清理可控、默认不看图、四个 agent 均能一步读取 notebook 指定输出,文本/HTML 不被无声改写。不是“CLI 成功导出了一张图片”就算完成整个 epic。
本文之外暂不扩展到宿主存储维护、Markdown attachments、完整 widget 状态还原、live kernel output 回溯或跨机器结果同步。