@jianxx/dsh-cc-tool-notebook-edit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.i18n.yaml +6 -0
- package/README.md +65 -0
- package/README.zh.md +65 -0
- package/lib/index.d.ts +59 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +281 -0
- package/lib/index.js.map +1 -0
- package/lib/invariant.d.ts +16 -0
- package/lib/invariant.d.ts.map +1 -0
- package/lib/invariant.js +23 -0
- package/lib/invariant.js.map +1 -0
- package/lib/notebook.d.ts +86 -0
- package/lib/notebook.d.ts.map +1 -0
- package/lib/notebook.js +108 -0
- package/lib/notebook.js.map +1 -0
- package/lib/render.d.ts +50 -0
- package/lib/render.d.ts.map +1 -0
- package/lib/render.js +52 -0
- package/lib/render.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/core/tool-notebook-edit/README.md
|
|
5
|
+
README.md: 7307c617dab2ba0afc80ecbeb0eba868bfafe4c6
|
|
6
|
+
README.zh.md: 06f4c83e57781420acb7dd4b4a404907a6138e9d
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @jianxx/dsh-cc-tool-notebook-edit
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Model-facing `NotebookEdit` tool that edits Jupyter notebook (.ipynb) cells, aligned to Claude Code's `NotebookEditTool` replace/insert/delete semantics and cell addressing. It registers into `ctx.tools` via the `@jianxx/dsh-cc-tools` `ToolRuntime` and reads/writes through the harness `ctx.fs` seam.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
### `NotebookEdit`
|
|
10
|
+
|
|
11
|
+
Edits one cell of a `.ipynb` notebook.
|
|
12
|
+
|
|
13
|
+
| Arg | Type | Notes |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `notebook_path` | string | Absolute path to the `.ipynb` file (must be absolute; non-.ipynb rejected). |
|
|
16
|
+
| `new_source` | string | The new source for the cell. |
|
|
17
|
+
| `cell_id` | string | Optional. The cell to edit; when `edit_mode=insert`, the new cell is inserted after this one (or at the beginning when omitted). |
|
|
18
|
+
| `cell_type` | `code` / `markdown` | Optional. Defaults to the current cell type; required for `insert`. |
|
|
19
|
+
| `edit_mode` | `replace` / `insert` / `delete` | Optional. Defaults to `replace`. |
|
|
20
|
+
|
|
21
|
+
Cell references address a cell by its real `id` first, then by the legacy zero-based `cell-<index>` form CC displays for id-less cells — so older notebooks (nbformat 4 < 4.5) are editable the same way CC edits them. Replace clears a code cell's `execution_count` and `outputs`; insert mints a fresh id on nbformat >= 4.5; delete removes the cell.
|
|
22
|
+
|
|
23
|
+
## Read-before-write gate
|
|
24
|
+
|
|
25
|
+
Matching CC's `validateInput`, the tool refuses to write a notebook the model never read, and refuses a stale write when the file changed since that read. The gate is driven by the harness observation seam: the plugin keeps a `readStates` map on `apply()` and records every present `fs/observed` observation (the event the Read/Write/Edit tools emit on commit), keyed by resolved path. On execute it rejects a path with no observed read, rejects a path whose current `ctx.fs.stat()` version no longer equals the observed one, and after writing re-baselines the map to the write outcome's version — so its own write counts as the latest read. The seam exposes an opaque `FsVersion` freshness token rather than an mtime; comparing that token is the seam's native staleness check and is strictly stronger than CC's mtime comparison.
|
|
26
|
+
|
|
27
|
+
The tool is `isConcurrencySafe = () => false`: it mutates shared cell state and the read baseline, so it must never overlap a sibling `NotebookEdit` call.
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
`Config` is schemastery-typed and currently empty (reserved for future caps):
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export const Config = z.object({})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Each `apply(ctx, config)` call registers the `NotebookEdit` tool into `ctx.tools` and its `fs/observed` listener. Requires a loaded `ctx.tools` and an active `ctx.fs` backend (e.g. `@deepseek-ai/dsh-fs-local`); the plugin stays pending until `inject: ['tools', 'fs']` is satisfied.
|
|
38
|
+
|
|
39
|
+
## Install / registration
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import * as ToolNotebookEdit from '@jianxx/dsh-cc-tool-notebook-edit'
|
|
43
|
+
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
|
44
|
+
|
|
45
|
+
await ctx.plugin(SystemPrompt)
|
|
46
|
+
await ctx.plugin(ToolRuntime) // @jianxx/dsh-cc-tools
|
|
47
|
+
await ctx.plugin(LocalFileSystem, { cwd }) // ctx.fs backend
|
|
48
|
+
await ctx.plugin(ToolNotebookEdit) // registers the NotebookEdit tool + listener
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Choice of semantics
|
|
52
|
+
|
|
53
|
+
- **replace / insert / delete** and **cell addressing** mirror CC's `NotebookEditTool` exactly, including the `cell-<n>` fallback for id-less notebooks and the clear-outputs-on-replace behavior.
|
|
54
|
+
- **Read-before-write gate** reproduces CC's "read first" / "modified since read" rejections as typed `NotebookEditError`s with stable codes.
|
|
55
|
+
- **Write re-baselines** so a second edit without a fresh Read is allowed, exactly as CC's `readFileState` is updated by its own write.
|
|
56
|
+
|
|
57
|
+
## Build order
|
|
58
|
+
|
|
59
|
+
`tool-notebook-edit` depends on the workspace `@jianxx/dsh-cc-tools` package and harness base packages (`@deepseek-ai/cordis`, `@deepseek-ai/dsh-fs`, `@deepseek-ai/dsh-llm`, `@deepseek-ai/dsh-invariants`, `@deepseek-ai/schemastery`). It has no dependency on any other workspace package, so it builds as soon as `core/tools` does; `tsc -b` resolves the reference order automatically.
|
|
60
|
+
|
|
61
|
+
## Known limitations
|
|
62
|
+
|
|
63
|
+
- The notebook is rewritten as single-space-indented JSON (matching CC's `IPYNB_INDENT = 1`); surrounding formatting is not preserved.
|
|
64
|
+
- `source` arrays in existing cells are replaced with the given `new_source` string rather than preserved as arrays (same as CC).
|
|
65
|
+
- The read baseline is plugin-lifetime: it resets on plugin reload (HMR), so a model that read a notebook before a reload is asked to Read again first.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @jianxx/dsh-cc-tool-notebook-edit
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
模型侧 `NotebookEdit` 工具:编辑 Jupyter notebook (.ipynb) 单元格,语义对齐 Claude Code 的 `NotebookEditTool` 的 replace/insert/delete 与单元格寻址。通过 `@jianxx/dsh-cc-tools` 的 `ToolRuntime` 注册进 `ctx.tools`,并通过 harness 的 `ctx.fs` seam 读写。
|
|
6
|
+
|
|
7
|
+
## 工具
|
|
8
|
+
|
|
9
|
+
### `NotebookEdit`
|
|
10
|
+
|
|
11
|
+
编辑某个 `.ipynb` notebook 的一个单元格。
|
|
12
|
+
|
|
13
|
+
| 参数 | 类型 | 说明 |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `notebook_path` | string | `.ipynb` 文件的绝对路径(必须为绝对路径;非 `.ipynb` 会被拒绝)。 |
|
|
16
|
+
| `new_source` | string | 该单元格的新源码。 |
|
|
17
|
+
| `cell_id` | string | 可选。要编辑的单元格;当 `edit_mode=insert` 时,新单元格插入到该单元格之后(省略时插到开头)。 |
|
|
18
|
+
| `cell_type` | `code` / `markdown` | 可选。默认沿用当前单元格类型;`insert` 时必须提供。 |
|
|
19
|
+
| `edit_mode` | `replace` / `insert` / `delete` | 可选。默认 `replace`。 |
|
|
20
|
+
|
|
21
|
+
单元格引用先按真实 `id` 寻址,再按 CC 为无 id 单元格展示的零基 `cell-<index>` 形式寻址——因此较老版本的 notebook(nbformat 4 且 minor < 4.5)也能像 CC 一样被编辑。replace 会清空 code 单元格的 `execution_count` 与 `outputs`;在 nbformat >= 4.5 上 insert 会生成新 id;delete 移除单元格。
|
|
22
|
+
|
|
23
|
+
## 写前先读(read-before-write)门
|
|
24
|
+
|
|
25
|
+
与 CC 的 `validateInput` 一致,工具拒绝写入模型从未读过的 notebook,也拒绝在文件自读取后被改动时(stale write)进行写入。该门由 harness 观测 seam 驱动:插件在 `apply()` 中维护一张 `readStates` 表,并记录每条 `present` 的 `fs/observed` 观测(即 Read/Write/Edit 工具在提交时发出的事件),按键为解析后的路径。execute 时:无已观测读记录 → 拒绝;当前 `ctx.fs.stat()` 版本与该观测版本不一致 → 拒绝。写完后用写入结果的版本重新建立基线——因此工具自身的写视为“已读”的最新基线。seam 暴露的是不透明 `FsVersion` 新鲜度令牌而非 mtime;比较该令牌是 seam 原生的陈旧性校验,严格强于 CC 的 mtime 比较。
|
|
26
|
+
|
|
27
|
+
该工具为 `isConcurrencySafe = () => false`:它会改动共享的单元格状态与读基线,因此绝不可与并行的兄弟 `NotebookEdit` 调用重叠。
|
|
28
|
+
|
|
29
|
+
## 配置
|
|
30
|
+
|
|
31
|
+
`Config` 采用 schemastery 类型,当前为空(预留未来上限):
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export const Config = z.object({})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
每次 `apply(ctx, config)` 都会把 `NotebookEdit` 工具注册进 `ctx.tools`,并注册其 `fs/observed` 监听器。需要已加载的 `ctx.tools` 与活跃的 `ctx.fs` 后端(例如 `@deepseek-ai/dsh-fs-local`);在 `inject: ['tools', 'fs']` 满足之前,插件保持等待状态。
|
|
38
|
+
|
|
39
|
+
## 安装 / 注册
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import * as ToolNotebookEdit from '@jianxx/dsh-cc-tool-notebook-edit'
|
|
43
|
+
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
|
|
44
|
+
|
|
45
|
+
await ctx.plugin(SystemPrompt)
|
|
46
|
+
await ctx.plugin(ToolRuntime) // @jianxx/dsh-cc-tools
|
|
47
|
+
await ctx.plugin(LocalFileSystem, { cwd }) // ctx.fs 后端
|
|
48
|
+
await ctx.plugin(ToolNotebookEdit) // 注册 NotebookEdit 工具 + 监听器
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 语义取舍
|
|
52
|
+
|
|
53
|
+
- **replace / insert / delete** 与**单元格寻址**完全对齐 CC 的 `NotebookEditTool`,包括无 id notebook 的 `cell-<n>` 回退,以及 replace 时清空 outputs。
|
|
54
|
+
- **写前先读门**将 CC 的“请先读”/“读取后被修改”拒绝复现为带稳定 code 的 `NotebookEditError`。
|
|
55
|
+
- **写后重设基线**:工具的写被视为最新读基线,第二次编辑无需重新 Read——与 CC 的 `readFileState` 被自身写入更新的行为一致。
|
|
56
|
+
|
|
57
|
+
## 构建顺序
|
|
58
|
+
|
|
59
|
+
`tool-notebook-edit` 仅依赖工作区 `@jianxx/dsh-cc-tools` 包与 harness 基础包(`@deepseek-ai/cordis`、`@deepseek-ai/dsh-fs`、`@deepseek-ai/dsh-llm`、`@deepseek-ai/dsh-invariants`、`@deepseek-ai/schemastery`)。它不依赖任何其它工作区包,因此只要 `core/tools` 构建完成即可构建;`tsc -b` 会自动解析引用顺序。
|
|
60
|
+
|
|
61
|
+
## 已知限制
|
|
62
|
+
|
|
63
|
+
- notebook 以单空格缩进的 JSON 重写(与 CC 的 `IPYNB_INDENT = 1` 一致);不保留原有排版。
|
|
64
|
+
- 现有单元格中的 `source` 数组会被替换为给定的 `new_source` 字符串,而非保留为数组(与 CC 一致)。
|
|
65
|
+
- 读基线存活期为插件实例:插件热重载(HMR)时会重置,因此 reload 前读过 notebook 的模型需要先重新 Read。
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing `NotebookEdit` tool that edits Jupyter notebook (.ipynb) cells
|
|
3
|
+
* over the `ctx.fs` seam, aligned with Claude Code's NotebookEditTool: the
|
|
4
|
+
* `replace` / `insert` / `delete` edit modes, `cell-<n>`-and-real-id cell
|
|
5
|
+
* addressing for id-less notebooks, and a strict read-before-write gate that
|
|
6
|
+
* refuses to write a notebook the model never read, or one whose file changed
|
|
7
|
+
* since that read.
|
|
8
|
+
*
|
|
9
|
+
* ## Read-before-write gate
|
|
10
|
+
*
|
|
11
|
+
* CC's validateInput requires a prior Read and a fresh file before any edit.
|
|
12
|
+
* This tool reproduces that over the harness observation seam: `apply()` keeps
|
|
13
|
+
* a plugin-lifetime `readStates` map (path → observed {@link FsVersion}) fed by
|
|
14
|
+
* the synchronous `fs/observed` event every time a model-facing read/write/edit
|
|
15
|
+
* commits a present observation. On execute the tool rejects a path with no
|
|
16
|
+
* observed read, rejects a path whose current `stat()` version no longer
|
|
17
|
+
* matches the observed one (an external change — CC's stale mtime check), and
|
|
18
|
+
* after writing re-baselines the map to the write outcome's version, so its own
|
|
19
|
+
* write counts as the latest read baseline. The seam exposes an authoritative
|
|
20
|
+
* opaque version token rather than an mtime; comparing that token is the seam's
|
|
21
|
+
* native staleness check and is strictly stronger than CC's mtime comparison.
|
|
22
|
+
* @module @jianxx/dsh-cc-tool-notebook-edit
|
|
23
|
+
*/
|
|
24
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
25
|
+
import z from '@deepseek-ai/schemastery';
|
|
26
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
27
|
+
export declare const name = "tool-notebook-edit";
|
|
28
|
+
export declare const inject: string[];
|
|
29
|
+
export { presentNotebookEditCall, presentNotebookEditResult } from './render.ts';
|
|
30
|
+
export type { NotebookEditArgs, NotebookEditResult } from './render.ts';
|
|
31
|
+
export { findCellIndex, languageOf, newCellId, parseCellId, parseNotebook, supportsCellIds, } from './notebook.ts';
|
|
32
|
+
export type { NotebookCell, NotebookContent, } from './notebook.ts';
|
|
33
|
+
/** Stable, machine-routable code for one NotebookEdit failure. */
|
|
34
|
+
export type NotebookEditErrorCode = 'NOTEBOOK_NOT_FOUND' | 'NOTEBOOK_NOT_IPYNB' | 'NOTEBOOK_INVALID_MODE' | 'NOTEBOOK_MISSING_CELL_TYPE' | 'NOTEBOOK_INVALID_JSON' | 'NOTEBOOK_MISSING_CELL_ID' | 'NOTEBOOK_INDEX_OUT_OF_RANGE' | 'NOTEBOOK_ID_NOT_FOUND' | 'NOTEBOOK_NOT_READ' | 'NOTEBOOK_STALE';
|
|
35
|
+
/**
|
|
36
|
+
* Typed NotebookEdit failure carrying a stable code (like `FsError`), so
|
|
37
|
+
* failure/retry layers can branch on the code rather than parsing messages.
|
|
38
|
+
*/
|
|
39
|
+
export declare class NotebookEditError extends HarnessError {
|
|
40
|
+
readonly code: NotebookEditErrorCode;
|
|
41
|
+
constructor(message: string, code: NotebookEditErrorCode, options?: ErrorOptions);
|
|
42
|
+
}
|
|
43
|
+
/** Plugin runtime configuration (reserved for future caps; none today). */
|
|
44
|
+
export interface Config {
|
|
45
|
+
/** No configuration today. */
|
|
46
|
+
readonly _?: never;
|
|
47
|
+
}
|
|
48
|
+
/** Runtime configuration schema. */
|
|
49
|
+
export declare const Config: z<Config>;
|
|
50
|
+
/**
|
|
51
|
+
* Register the NotebookEdit tool and its plugin-lifetime read-state recorder.
|
|
52
|
+
* A single `fs/observed` listener feeds a `readStates` map (path → observed
|
|
53
|
+
* version); the tool is registered once and lives for the whole plugin mount,
|
|
54
|
+
* so there is no per-call state to reconstruct. cordis auto-disposes both the
|
|
55
|
+
* listener and the tool registration when the plugin's scope disposes.
|
|
56
|
+
* @param ctx - active Cordis context.
|
|
57
|
+
*/
|
|
58
|
+
export declare function apply(ctx: Context): void;
|
|
59
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAkBnD,eAAO,MAAM,IAAI,uBAAuB,CAAA;AACxC,eAAO,MAAM,MAAM,UAAkB,CAAA;AAErC,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAA;AAChF,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,EACL,aAAa,EACb,UAAU,EACV,SAAS,EACT,WAAW,EACX,aAAa,EACb,eAAe,GAChB,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAA;AAQtB,kEAAkE;AAClE,MAAM,MAAM,qBAAqB,GAC7B,oBAAoB,GACpB,oBAAoB,GACpB,uBAAuB,GACvB,4BAA4B,GAC5B,uBAAuB,GACvB,0BAA0B,GAC1B,6BAA6B,GAC7B,uBAAuB,GACvB,mBAAmB,GACnB,gBAAgB,CAAA;AAEpB;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,SAAkB,IAAI,EAAE,qBAAqB,CAAA;gBAEjC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,CAAC,EAAE,YAAY;CAKjF;AAED,2EAA2E;AAC3E,MAAM,WAAW,MAAM;IACrB,8BAA8B;IAC9B,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,CAAA;CACnB;AAED,oCAAoC;AACpC,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAAgB,CAAA;AAE7C;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CA0FxC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing `NotebookEdit` tool that edits Jupyter notebook (.ipynb) cells
|
|
3
|
+
* over the `ctx.fs` seam, aligned with Claude Code's NotebookEditTool: the
|
|
4
|
+
* `replace` / `insert` / `delete` edit modes, `cell-<n>`-and-real-id cell
|
|
5
|
+
* addressing for id-less notebooks, and a strict read-before-write gate that
|
|
6
|
+
* refuses to write a notebook the model never read, or one whose file changed
|
|
7
|
+
* since that read.
|
|
8
|
+
*
|
|
9
|
+
* ## Read-before-write gate
|
|
10
|
+
*
|
|
11
|
+
* CC's validateInput requires a prior Read and a fresh file before any edit.
|
|
12
|
+
* This tool reproduces that over the harness observation seam: `apply()` keeps
|
|
13
|
+
* a plugin-lifetime `readStates` map (path → observed {@link FsVersion}) fed by
|
|
14
|
+
* the synchronous `fs/observed` event every time a model-facing read/write/edit
|
|
15
|
+
* commits a present observation. On execute the tool rejects a path with no
|
|
16
|
+
* observed read, rejects a path whose current `stat()` version no longer
|
|
17
|
+
* matches the observed one (an external change — CC's stale mtime check), and
|
|
18
|
+
* after writing re-baselines the map to the write outcome's version, so its own
|
|
19
|
+
* write counts as the latest read baseline. The seam exposes an authoritative
|
|
20
|
+
* opaque version token rather than an mtime; comparing that token is the seam's
|
|
21
|
+
* native staleness check and is strictly stronger than CC's mtime comparison.
|
|
22
|
+
* @module @jianxx/dsh-cc-tool-notebook-edit
|
|
23
|
+
*/
|
|
24
|
+
import { extname } from 'node:path';
|
|
25
|
+
import z from '@deepseek-ai/schemastery';
|
|
26
|
+
import { HarnessError } from '@deepseek-ai/dsh-llm';
|
|
27
|
+
import { defineTool } from '@jianxx/dsh-cc-tools';
|
|
28
|
+
import { findCellIndex, languageOf, newCellId, parseNotebook, supportsCellIds, } from "./notebook.js";
|
|
29
|
+
import { presentNotebookEditCall, presentNotebookEditResult, } from "./render.js";
|
|
30
|
+
export const name = 'tool-notebook-edit';
|
|
31
|
+
export const inject = ['tools', 'fs'];
|
|
32
|
+
export { presentNotebookEditCall, presentNotebookEditResult } from "./render.js";
|
|
33
|
+
export { findCellIndex, languageOf, newCellId, parseCellId, parseNotebook, supportsCellIds, } from "./notebook.js";
|
|
34
|
+
/**
|
|
35
|
+
* Canonical JSON indentation for the rewritten .ipynb — a single space, the
|
|
36
|
+
* same `IPYNB_INDENT = 1` CC uses when it writes a notebook back.
|
|
37
|
+
*/
|
|
38
|
+
const IPYNB_INDENT = 1;
|
|
39
|
+
/**
|
|
40
|
+
* Typed NotebookEdit failure carrying a stable code (like `FsError`), so
|
|
41
|
+
* failure/retry layers can branch on the code rather than parsing messages.
|
|
42
|
+
*/
|
|
43
|
+
export class NotebookEditError extends HarnessError {
|
|
44
|
+
code;
|
|
45
|
+
constructor(message, code, options) {
|
|
46
|
+
super(message, code, options);
|
|
47
|
+
this.name = 'NotebookEditError';
|
|
48
|
+
this.code = code;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Runtime configuration schema. */
|
|
52
|
+
export const Config = z.object({});
|
|
53
|
+
/**
|
|
54
|
+
* Register the NotebookEdit tool and its plugin-lifetime read-state recorder.
|
|
55
|
+
* A single `fs/observed` listener feeds a `readStates` map (path → observed
|
|
56
|
+
* version); the tool is registered once and lives for the whole plugin mount,
|
|
57
|
+
* so there is no per-call state to reconstruct. cordis auto-disposes both the
|
|
58
|
+
* listener and the tool registration when the plugin's scope disposes.
|
|
59
|
+
* @param ctx - active Cordis context.
|
|
60
|
+
*/
|
|
61
|
+
export function apply(ctx) {
|
|
62
|
+
const readStates = new Map();
|
|
63
|
+
// Cordis auto-clears this listener on plugin dispose (the dispose contract
|
|
64
|
+
// for this tool's subscription).
|
|
65
|
+
ctx.on('fs/observed', (target, observation) => {
|
|
66
|
+
// Synchronous recorder (must not throw: fs/observed is an emit event and a
|
|
67
|
+
// throw would fail the observing tool's already-committed call). Every
|
|
68
|
+
// present observation — read, write, or edit — becomes the baseline for
|
|
69
|
+
// that path, matching CC's readFileState being (re)set by Read/Write/Edit.
|
|
70
|
+
if (observation.kind === 'present') {
|
|
71
|
+
readStates.set(target.displayPath, observation.version);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
ctx.tools.register(defineTool({
|
|
75
|
+
name: 'NotebookEdit',
|
|
76
|
+
description: 'Replace the contents of a specific cell in a Jupyter notebook. '
|
|
77
|
+
+ 'Use edit_mode=insert to add a new cell after the cell specified by cell_id (or at the beginning when cell_id is omitted), '
|
|
78
|
+
+ 'and edit_mode=delete to remove it. Cell references address a cell by its id, '
|
|
79
|
+
+ 'or by its zero-based cell-<index>.',
|
|
80
|
+
parameters: {
|
|
81
|
+
notebook_path: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'The absolute path to the Jupyter notebook file to edit (must be absolute, not relative)',
|
|
84
|
+
required: true,
|
|
85
|
+
},
|
|
86
|
+
cell_id: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description: 'The ID of the cell to edit. When inserting a new cell, the new cell will be inserted after the cell with this ID, or at the beginning if not specified.',
|
|
89
|
+
},
|
|
90
|
+
new_source: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'The new source for the cell',
|
|
93
|
+
required: true,
|
|
94
|
+
},
|
|
95
|
+
cell_type: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
enum: ['code', 'markdown'],
|
|
98
|
+
description: 'The type of the cell (code or markdown). If not specified, it defaults to the current cell type. If using edit_mode=insert, this is required.',
|
|
99
|
+
},
|
|
100
|
+
edit_mode: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
enum: ['replace', 'insert', 'delete'],
|
|
103
|
+
description: 'The type of edit to make (replace, insert, delete). Defaults to replace.',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
output: {
|
|
107
|
+
schema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
additionalProperties: false,
|
|
110
|
+
properties: {
|
|
111
|
+
new_source: { type: 'string', required: true },
|
|
112
|
+
cell_id: { type: 'string' },
|
|
113
|
+
cell_type: { type: 'string', enum: ['code', 'markdown'], required: true },
|
|
114
|
+
language: { type: 'string', required: true },
|
|
115
|
+
edit_mode: { type: 'string', required: true },
|
|
116
|
+
error: { type: 'string', required: true },
|
|
117
|
+
notebook_path: { type: 'string', required: true },
|
|
118
|
+
original_file: { type: 'string', required: true },
|
|
119
|
+
updated_file: { type: 'string', required: true },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
render: (_args, value) => {
|
|
123
|
+
if (value.error !== '') {
|
|
124
|
+
return [{ type: 'text', text: value.error }];
|
|
125
|
+
}
|
|
126
|
+
switch (value.edit_mode) {
|
|
127
|
+
case 'insert':
|
|
128
|
+
return [{ type: 'text', text: `Inserted cell ${value.cell_id ?? ''} with ${value.new_source}` }];
|
|
129
|
+
case 'delete':
|
|
130
|
+
return [{ type: 'text', text: `Deleted cell ${value.cell_id ?? ''}` }];
|
|
131
|
+
default:
|
|
132
|
+
return [{ type: 'text', text: `Updated cell ${value.cell_id ?? ''} with ${value.new_source}` }];
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
presentCall: presentNotebookEditCall,
|
|
137
|
+
presentResult: (args, result) => presentNotebookEditResult(args, result),
|
|
138
|
+
isConcurrencySafe: () => false,
|
|
139
|
+
async execute(args, exec) {
|
|
140
|
+
return editNotebook(ctx, readStates, args, exec);
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Run one NotebookEdit call. Lives in a separate function (called from the
|
|
146
|
+
* registered closure) so the whole validation → read → gate → edit → write →
|
|
147
|
+
* re-baseline flow is a single testable unit.
|
|
148
|
+
*/
|
|
149
|
+
async function editNotebook(ctx, readStates, args, exec) {
|
|
150
|
+
const target = await ctx.fs.resolve(args.notebook_path, { signal: exec.signal });
|
|
151
|
+
// ── static validation (CC validateInput order) ────────────────────────
|
|
152
|
+
if (extname(target.displayPath) !== '.ipynb') {
|
|
153
|
+
throw new NotebookEditError('File must be a Jupyter notebook (.ipynb file). For editing other file types, use the FileEdit tool.', 'NOTEBOOK_NOT_IPYNB');
|
|
154
|
+
}
|
|
155
|
+
const editMode = args.edit_mode ?? 'replace';
|
|
156
|
+
if (editMode !== 'replace' && editMode !== 'insert' && editMode !== 'delete') {
|
|
157
|
+
throw new NotebookEditError('Edit mode must be replace, insert, or delete.', 'NOTEBOOK_INVALID_MODE');
|
|
158
|
+
}
|
|
159
|
+
if (editMode === 'insert' && !args.cell_type) {
|
|
160
|
+
throw new NotebookEditError('Cell type is required when using edit_mode=insert.', 'NOTEBOOK_MISSING_CELL_TYPE');
|
|
161
|
+
}
|
|
162
|
+
// ── read-before-write gate ─────────────────────────────────────────────
|
|
163
|
+
// (a) no observed read → refuse; (b) file changed since that read → refuse.
|
|
164
|
+
const observed = readStates.get(target.displayPath);
|
|
165
|
+
if (observed === undefined) {
|
|
166
|
+
throw new NotebookEditError('File has not been read yet. Read it first before writing to it.', 'NOTEBOOK_NOT_READ');
|
|
167
|
+
}
|
|
168
|
+
const info = await ctx.fs.stat(target, exec.signal);
|
|
169
|
+
if (info === undefined) {
|
|
170
|
+
throw new NotebookEditError('Notebook file does not exist.', 'NOTEBOOK_NOT_FOUND');
|
|
171
|
+
}
|
|
172
|
+
if (info.version !== observed) {
|
|
173
|
+
throw new NotebookEditError('File has been modified since read, either by the user or by a linter. Read it again before attempting to write it.', 'NOTEBOOK_STALE');
|
|
174
|
+
}
|
|
175
|
+
// ── read + parse ───────────────────────────────────────────────────────
|
|
176
|
+
let content;
|
|
177
|
+
try {
|
|
178
|
+
content = await ctx.fs.readText(target, exec.signal);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (isNotFound(error)) {
|
|
182
|
+
throw new NotebookEditError('Notebook file does not exist.', 'NOTEBOOK_NOT_FOUND');
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
const notebook = parseNotebook(content);
|
|
187
|
+
if (notebook === null) {
|
|
188
|
+
throw new NotebookEditError('Notebook is not valid JSON.', 'NOTEBOOK_INVALID_JSON');
|
|
189
|
+
}
|
|
190
|
+
// ── cell addressing (CC call semantics) ────────────────────────────────
|
|
191
|
+
let cellIndex;
|
|
192
|
+
if (args.cell_id === undefined) {
|
|
193
|
+
if (editMode !== 'insert') {
|
|
194
|
+
throw new NotebookEditError('Cell ID must be specified when not inserting a new cell.', 'NOTEBOOK_MISSING_CELL_ID');
|
|
195
|
+
}
|
|
196
|
+
cellIndex = 0; // CC defaults to inserting at the beginning.
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const resolved = findCellIndex(notebook.cells, args.cell_id);
|
|
200
|
+
if (!resolved.found) {
|
|
201
|
+
const parsed = /^cell-(\d+)$/.exec(args.cell_id);
|
|
202
|
+
if (parsed !== null && parsed[1] !== undefined) {
|
|
203
|
+
const index = Number.parseInt(parsed[1], 10);
|
|
204
|
+
if (!Number.isNaN(index) && index >= notebook.cells.length) {
|
|
205
|
+
throw new NotebookEditError(`Cell with index ${index} does not exist in notebook.`, 'NOTEBOOK_INDEX_OUT_OF_RANGE');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
throw new NotebookEditError(`Cell with ID "${args.cell_id}" not found in notebook.`, 'NOTEBOOK_ID_NOT_FOUND');
|
|
209
|
+
}
|
|
210
|
+
cellIndex = resolved.index;
|
|
211
|
+
if (editMode === 'insert')
|
|
212
|
+
cellIndex += 1; // insert after the addressed cell
|
|
213
|
+
}
|
|
214
|
+
// ── apply the edit ─────────────────────────────────────────────────────
|
|
215
|
+
let appliedMode = editMode;
|
|
216
|
+
let insertedIndex;
|
|
217
|
+
if (appliedMode === 'insert') {
|
|
218
|
+
insertedIndex = cellIndex;
|
|
219
|
+
spliceInsert(notebook, cellIndex, args.new_source, args.cell_type ?? 'code', supportsCellIds(notebook) ? newCellId() : undefined);
|
|
220
|
+
}
|
|
221
|
+
else if (appliedMode === 'delete') {
|
|
222
|
+
notebook.cells.splice(cellIndex, 1);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const targetCell = notebook.cells[cellIndex];
|
|
226
|
+
if (targetCell === undefined) {
|
|
227
|
+
throw new NotebookEditError(`Cell with index ${cellIndex} does not exist in notebook.`, 'NOTEBOOK_INDEX_OUT_OF_RANGE');
|
|
228
|
+
}
|
|
229
|
+
targetCell.source = args.new_source;
|
|
230
|
+
if (targetCell.cell_type === 'code') {
|
|
231
|
+
targetCell.execution_count = null;
|
|
232
|
+
targetCell.outputs = [];
|
|
233
|
+
}
|
|
234
|
+
if (args.cell_type && args.cell_type !== targetCell.cell_type) {
|
|
235
|
+
targetCell.cell_type = args.cell_type;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// ── write back + re-baseline ───────────────────────────────────────────
|
|
239
|
+
const updatedContent = JSON.stringify(notebook, null, IPYNB_INDENT);
|
|
240
|
+
let outcome;
|
|
241
|
+
try {
|
|
242
|
+
outcome = await ctx.fs.writeText(target, updatedContent, undefined, exec.signal);
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
if (isNotFound(error)) {
|
|
246
|
+
throw new NotebookEditError('Notebook file does not exist.', 'NOTEBOOK_NOT_FOUND');
|
|
247
|
+
}
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
// This tool's write becomes the latest observed baseline (CC: write sets
|
|
251
|
+
// readFileState to the post-write mtime).
|
|
252
|
+
readStates.set(target.displayPath, outcome.version);
|
|
253
|
+
const resultCellId = insertedIndex !== undefined ? notebook.cells[insertedIndex]?.id : args.cell_id;
|
|
254
|
+
return {
|
|
255
|
+
new_source: args.new_source,
|
|
256
|
+
...(resultCellId !== undefined ? { cell_id: resultCellId } : {}),
|
|
257
|
+
cell_type: args.cell_type ?? (appliedMode === 'delete'
|
|
258
|
+
? 'code'
|
|
259
|
+
: notebook.cells[insertedIndex ?? cellIndex]?.cell_type ?? 'code'),
|
|
260
|
+
language: languageOf(notebook),
|
|
261
|
+
edit_mode: appliedMode,
|
|
262
|
+
error: '',
|
|
263
|
+
notebook_path: target.displayPath,
|
|
264
|
+
original_file: content,
|
|
265
|
+
updated_file: updatedContent,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** Insert one new cell (mirroring CC's code vs markdown cell construction). */
|
|
269
|
+
function spliceInsert(notebook, index, source, type, id) {
|
|
270
|
+
const idPart = id !== undefined ? { id } : {};
|
|
271
|
+
const cell = type === 'markdown'
|
|
272
|
+
? { cell_type: 'markdown', ...idPart, source, metadata: {} }
|
|
273
|
+
: { cell_type: 'code', ...idPart, source, metadata: {}, execution_count: null, outputs: [] };
|
|
274
|
+
notebook.cells.splice(index, 0, cell);
|
|
275
|
+
}
|
|
276
|
+
/** Whether a thrown error from ctx.fs signals a missing target. */
|
|
277
|
+
function isNotFound(error) {
|
|
278
|
+
return (error instanceof Error
|
|
279
|
+
&& error.code === 'FS_NOT_FOUND');
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@jianxx/dsh-cc-tool-notebook-edit`.
|
|
3
|
+
* @module @jianxx/dsh-cc-tool-notebook-edit/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "tool-notebook-edit-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,oCAAoC;AACpC,eAAO,MAAM,IAAI,iCAAiC,CAAA;AAClD,2EAA2E;AAC3E,eAAO,MAAM,MAAM,UAAiB,CAAA;AASpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,OAAO,CAAC,MAAM,IAAI,CACU,CAAA"}
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@jianxx/dsh-cc-tool-notebook-edit`.
|
|
3
|
+
* @module @jianxx/dsh-cc-tool-notebook-edit/invariant
|
|
4
|
+
*/
|
|
5
|
+
const PACKAGE_NAME = '@jianxx/dsh-cc-tool-notebook-edit';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export const name = 'tool-notebook-edit-invariant';
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export const inject = ['invariants'];
|
|
10
|
+
/**
|
|
11
|
+
* No durable runtime invariant: the read-before-write baseline lives only in
|
|
12
|
+
* the plugin's `apply`-registered closure and is torn down with the plugin
|
|
13
|
+
* scope, so there is no persistent projection a companion could cross-check.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => { };
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
/* jscpd:ignore-end */
|
|
23
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariant.js","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,YAAY,GAAG,mCAAmC,CAAA;AAExD,oCAAoC;AACpC,MAAM,CAAC,MAAM,IAAI,GAAG,8BAA8B,CAAA;AAClD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,CAAA;AAEpC;;;;GAIG;AACH,MAAM,OAAO,GAAuB,GAAG,EAAE,GAAE,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAuB,EAAE,CACzD,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AACjE,sBAAsB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Jupyter notebook (.ipynb) model: JSON parsing, cell addressing, and
|
|
3
|
+
* generation helpers. No I/O — everything here is a function of its inputs, so
|
|
4
|
+
* the replace/insert/delete round-trips and the cell-address edge cases are
|
|
5
|
+
* directly unit-testable apart from the fs seam.
|
|
6
|
+
*
|
|
7
|
+
* Cell addressing matches Claude Code's NotebookEditTool: a `cell_id` addresses
|
|
8
|
+
* a cell by its real `id` first; when no cell carries that id, the legacy
|
|
9
|
+
* `cell-<index>` form addresses the cell at the zero-based `<index>` (the id a
|
|
10
|
+
* cell without an `id` field is displayed as). See `parseCellId`.
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/** One callable (code/markdown) notebook cell. */
|
|
14
|
+
export interface NotebookCell {
|
|
15
|
+
cell_type: 'code' | 'markdown';
|
|
16
|
+
id?: string;
|
|
17
|
+
source: string | string[];
|
|
18
|
+
metadata: Record<string, unknown>;
|
|
19
|
+
execution_count?: number | null;
|
|
20
|
+
outputs?: unknown[];
|
|
21
|
+
}
|
|
22
|
+
/** A parsed Jupyter notebook (.ipynb) document. */
|
|
23
|
+
export interface NotebookContent {
|
|
24
|
+
cells: NotebookCell[];
|
|
25
|
+
metadata: {
|
|
26
|
+
language_info?: {
|
|
27
|
+
name?: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
nbformat: number;
|
|
31
|
+
nbformat_minor: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse raw notebook text into the structural subset this tool edits, or
|
|
35
|
+
* `null` when the text is not valid JSON or lacks a `cells` array. CC rejects a
|
|
36
|
+
* non-JSON notebook with "Notebook is not valid JSON."; a malformed-but-JSON
|
|
37
|
+
* body (missing `cells`) is treated as not a valid notebook for the same guard,
|
|
38
|
+
* because there is no cell list to address.
|
|
39
|
+
* @param content - the raw file text.
|
|
40
|
+
* @returns the parsed notebook, or `null` when unparseable.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseNotebook(content: string): NotebookContent | null;
|
|
43
|
+
/**
|
|
44
|
+
* Parse a `cell-<index>` cell reference into its zero-based index. Any other
|
|
45
|
+
* form (including a bare numeric string, which is never a valid ipynb cell
|
|
46
|
+
* identifier) yields `undefined`.
|
|
47
|
+
* @param cellId - the cell identifier to parse.
|
|
48
|
+
* @returns the zero-based index, or `undefined` when not a `cell-<n>` form.
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseCellId(cellId: string): number | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve a `cell_id` to a cell position. CC matches the real `id` first, then
|
|
53
|
+
* falls back to the legacy zero-based `cell-<index>` form. Returns the resolved
|
|
54
|
+
* index and whether it was found, or -1 / false when the identifier addresses
|
|
55
|
+
* no cell.
|
|
56
|
+
* @param cells - the notebook's cell list.
|
|
57
|
+
* @param cellId - the identifier to resolve.
|
|
58
|
+
* @returns a found cell index, or `{ index: -1, found: false }`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function findCellIndex(cells: readonly NotebookCell[], cellId: string): {
|
|
61
|
+
index: number;
|
|
62
|
+
found: boolean;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* The notebook's programming language, defaulting to `python` when the metadata
|
|
66
|
+
* does not name one — the same default CC uses in `NotebookEditTool.call()`.
|
|
67
|
+
* @param notebook - the parsed notebook.
|
|
68
|
+
* @returns the language_info name, or `'python'`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function languageOf(notebook: NotebookContent): string;
|
|
71
|
+
/**
|
|
72
|
+
* Whether this notebook's format version carries real cell ids (nbformat > 4,
|
|
73
|
+
* or 4 with minor >= 5). Only such notebooks receive a freshly minted id on
|
|
74
|
+
* insert / echo the target id on replace; older notebooks keep their ids as-is
|
|
75
|
+
* (CC only sets the new id under this version gate).
|
|
76
|
+
* @param notebook - the parsed notebook.
|
|
77
|
+
* @returns whether the version supports labelled cell ids.
|
|
78
|
+
*/
|
|
79
|
+
export declare function supportsCellIds(notebook: NotebookContent): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Mint a fresh, collision-safe cell id: a base-36 string, matching CC's
|
|
82
|
+
* `Math.random().toString(36).substring(2, 15)` id generator.
|
|
83
|
+
* @returns a new cell id.
|
|
84
|
+
*/
|
|
85
|
+
export declare function newCellId(): string;
|
|
86
|
+
//# sourceMappingURL=notebook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notebook.d.ts","sourceRoot":"","sources":["../src/notebook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,EAAE,CAAA;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,QAAQ,EAAE;QAAE,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAoBrE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,MAAM,GACb;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAQnC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAKlE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC"}
|
package/lib/notebook.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Jupyter notebook (.ipynb) model: JSON parsing, cell addressing, and
|
|
3
|
+
* generation helpers. No I/O — everything here is a function of its inputs, so
|
|
4
|
+
* the replace/insert/delete round-trips and the cell-address edge cases are
|
|
5
|
+
* directly unit-testable apart from the fs seam.
|
|
6
|
+
*
|
|
7
|
+
* Cell addressing matches Claude Code's NotebookEditTool: a `cell_id` addresses
|
|
8
|
+
* a cell by its real `id` first; when no cell carries that id, the legacy
|
|
9
|
+
* `cell-<index>` form addresses the cell at the zero-based `<index>` (the id a
|
|
10
|
+
* cell without an `id` field is displayed as). See `parseCellId`.
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Parse raw notebook text into the structural subset this tool edits, or
|
|
15
|
+
* `null` when the text is not valid JSON or lacks a `cells` array. CC rejects a
|
|
16
|
+
* non-JSON notebook with "Notebook is not valid JSON."; a malformed-but-JSON
|
|
17
|
+
* body (missing `cells`) is treated as not a valid notebook for the same guard,
|
|
18
|
+
* because there is no cell list to address.
|
|
19
|
+
* @param content - the raw file text.
|
|
20
|
+
* @returns the parsed notebook, or `null` when unparseable.
|
|
21
|
+
*/
|
|
22
|
+
export function parseNotebook(content) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = JSON.parse(content);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (raw === null || typeof raw !== 'object')
|
|
31
|
+
return null;
|
|
32
|
+
const body = raw;
|
|
33
|
+
const cells = body['cells'];
|
|
34
|
+
if (!Array.isArray(cells))
|
|
35
|
+
return null;
|
|
36
|
+
const metadata = body['metadata'];
|
|
37
|
+
const nbformat = Number(body['nbformat']);
|
|
38
|
+
const nbformatMinor = Number(body['nbformat_minor']);
|
|
39
|
+
return {
|
|
40
|
+
cells: cells,
|
|
41
|
+
metadata: metadata,
|
|
42
|
+
nbformat,
|
|
43
|
+
nbformat_minor: nbformatMinor,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse a `cell-<index>` cell reference into its zero-based index. Any other
|
|
48
|
+
* form (including a bare numeric string, which is never a valid ipynb cell
|
|
49
|
+
* identifier) yields `undefined`.
|
|
50
|
+
* @param cellId - the cell identifier to parse.
|
|
51
|
+
* @returns the zero-based index, or `undefined` when not a `cell-<n>` form.
|
|
52
|
+
*/
|
|
53
|
+
export function parseCellId(cellId) {
|
|
54
|
+
const match = /^cell-(\d+)$/.exec(cellId);
|
|
55
|
+
if (match === null || match[1] === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
const index = Number.parseInt(match[1], 10);
|
|
58
|
+
return Number.isNaN(index) ? undefined : index;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve a `cell_id` to a cell position. CC matches the real `id` first, then
|
|
62
|
+
* falls back to the legacy zero-based `cell-<index>` form. Returns the resolved
|
|
63
|
+
* index and whether it was found, or -1 / false when the identifier addresses
|
|
64
|
+
* no cell.
|
|
65
|
+
* @param cells - the notebook's cell list.
|
|
66
|
+
* @param cellId - the identifier to resolve.
|
|
67
|
+
* @returns a found cell index, or `{ index: -1, found: false }`.
|
|
68
|
+
*/
|
|
69
|
+
export function findCellIndex(cells, cellId) {
|
|
70
|
+
const byId = cells.findIndex(cell => cell.id === cellId);
|
|
71
|
+
if (byId !== -1)
|
|
72
|
+
return { index: byId, found: true };
|
|
73
|
+
const parsed = parseCellId(cellId);
|
|
74
|
+
if (parsed !== undefined && parsed < cells.length) {
|
|
75
|
+
return { index: parsed, found: true };
|
|
76
|
+
}
|
|
77
|
+
return { index: -1, found: false };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The notebook's programming language, defaulting to `python` when the metadata
|
|
81
|
+
* does not name one — the same default CC uses in `NotebookEditTool.call()`.
|
|
82
|
+
* @param notebook - the parsed notebook.
|
|
83
|
+
* @returns the language_info name, or `'python'`.
|
|
84
|
+
*/
|
|
85
|
+
export function languageOf(notebook) {
|
|
86
|
+
return notebook.metadata.language_info?.name ?? 'python';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether this notebook's format version carries real cell ids (nbformat > 4,
|
|
90
|
+
* or 4 with minor >= 5). Only such notebooks receive a freshly minted id on
|
|
91
|
+
* insert / echo the target id on replace; older notebooks keep their ids as-is
|
|
92
|
+
* (CC only sets the new id under this version gate).
|
|
93
|
+
* @param notebook - the parsed notebook.
|
|
94
|
+
* @returns whether the version supports labelled cell ids.
|
|
95
|
+
*/
|
|
96
|
+
export function supportsCellIds(notebook) {
|
|
97
|
+
return (notebook.nbformat > 4 ||
|
|
98
|
+
(notebook.nbformat === 4 && notebook.nbformat_minor >= 5));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Mint a fresh, collision-safe cell id: a base-36 string, matching CC's
|
|
102
|
+
* `Math.random().toString(36).substring(2, 15)` id generator.
|
|
103
|
+
* @returns a new cell id.
|
|
104
|
+
*/
|
|
105
|
+
export function newCellId() {
|
|
106
|
+
return Math.random().toString(36).substring(2, 15);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=notebook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notebook.js","sourceRoot":"","sources":["../src/notebook.ts"],"names":[],"mappings":"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"}
|
package/lib/render.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure presentation (presentCall / presentResult) for the NotebookEdit tool: a
|
|
3
|
+
* generic pending card naming the target notebook and the resolved edit mode,
|
|
4
|
+
* and a generic result card that fences errors so a failure reads distinct
|
|
5
|
+
* from a success summary.
|
|
6
|
+
* @module @jianxx/dsh-cc-tool-notebook-edit/render
|
|
7
|
+
*/
|
|
8
|
+
import type { GenericCallView, ToolResult, ToolResultView } from '@jianxx/dsh-cc-tools';
|
|
9
|
+
/** Arguments accepted by the NotebookEdit tool. */
|
|
10
|
+
export interface NotebookEditArgs {
|
|
11
|
+
/** Absolute path to the Jupyter notebook to edit. */
|
|
12
|
+
notebook_path: string;
|
|
13
|
+
/** The cell to edit; omitted inserts at the beginning when mode is insert. */
|
|
14
|
+
cell_id?: string;
|
|
15
|
+
/** The new source for the cell. */
|
|
16
|
+
new_source: string;
|
|
17
|
+
/** The cell type (code/markdown); required for insert, else defaults. */
|
|
18
|
+
cell_type?: 'code' | 'markdown';
|
|
19
|
+
/** replace (default), insert, or delete. */
|
|
20
|
+
edit_mode?: 'replace' | 'insert' | 'delete';
|
|
21
|
+
}
|
|
22
|
+
/** The canonical result of a NotebookEdit call. */
|
|
23
|
+
export interface NotebookEditResult {
|
|
24
|
+
new_source: string;
|
|
25
|
+
cell_id?: string;
|
|
26
|
+
cell_type: 'code' | 'markdown';
|
|
27
|
+
language: string;
|
|
28
|
+
edit_mode: string;
|
|
29
|
+
error: string;
|
|
30
|
+
notebook_path: string;
|
|
31
|
+
original_file: string;
|
|
32
|
+
updated_file: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Present a NotebookEdit pending call as a generic execute card naming the
|
|
36
|
+
* target notebook and the requested edit mode.
|
|
37
|
+
* @param args - validated tool arguments.
|
|
38
|
+
* @returns a generic card view.
|
|
39
|
+
*/
|
|
40
|
+
export declare function presentNotebookEditCall(args: NotebookEditArgs): GenericCallView;
|
|
41
|
+
/**
|
|
42
|
+
* Present a NotebookEdit result as a generic card. Success shows the written
|
|
43
|
+
* cell id and source; a failed value (error text present) is fenced so it reads
|
|
44
|
+
* distinct from a success summary.
|
|
45
|
+
* @param _args - validated tool arguments (unused by the presenter).
|
|
46
|
+
* @param result - the finalized tool result.
|
|
47
|
+
* @returns a generic card view, or `undefined` for an unexpected shape.
|
|
48
|
+
*/
|
|
49
|
+
export declare function presentNotebookEditResult(_args: unknown, result: ToolResult): ToolResultView | undefined;
|
|
50
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACf,MAAM,sBAAsB,CAAA;AAE7B,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAA;IACrB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mCAAmC;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC/B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;CAC5C;AAED,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,gBAAgB,GACrB,eAAe,CAiBjB;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,UAAU,GACjB,cAAc,GAAG,SAAS,CAW5B"}
|
package/lib/render.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure presentation (presentCall / presentResult) for the NotebookEdit tool: a
|
|
3
|
+
* generic pending card naming the target notebook and the resolved edit mode,
|
|
4
|
+
* and a generic result card that fences errors so a failure reads distinct
|
|
5
|
+
* from a success summary.
|
|
6
|
+
* @module @jianxx/dsh-cc-tool-notebook-edit/render
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Present a NotebookEdit pending call as a generic execute card naming the
|
|
10
|
+
* target notebook and the requested edit mode.
|
|
11
|
+
* @param args - validated tool arguments.
|
|
12
|
+
* @returns a generic card view.
|
|
13
|
+
*/
|
|
14
|
+
export function presentNotebookEditCall(args) {
|
|
15
|
+
const mode = args.edit_mode ?? 'replace';
|
|
16
|
+
return {
|
|
17
|
+
card: 'generic',
|
|
18
|
+
title: 'Editing Notebook',
|
|
19
|
+
kind: 'execute',
|
|
20
|
+
rawInput: `${args.notebook_path} (${mode})`,
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: args.cell_id === undefined
|
|
25
|
+
? `${mode} in ${args.notebook_path}`
|
|
26
|
+
: `${mode} cell ${args.cell_id} in ${args.notebook_path}`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Present a NotebookEdit result as a generic card. Success shows the written
|
|
33
|
+
* cell id and source; a failed value (error text present) is fenced so it reads
|
|
34
|
+
* distinct from a success summary.
|
|
35
|
+
* @param _args - validated tool arguments (unused by the presenter).
|
|
36
|
+
* @param result - the finalized tool result.
|
|
37
|
+
* @returns a generic card view, or `undefined` for an unexpected shape.
|
|
38
|
+
*/
|
|
39
|
+
export function presentNotebookEditResult(_args, result) {
|
|
40
|
+
const block = result.content.length === 1 ? result.content[0] : undefined;
|
|
41
|
+
if (block === undefined || block.type !== 'text')
|
|
42
|
+
return undefined;
|
|
43
|
+
return result.isError
|
|
44
|
+
? {
|
|
45
|
+
card: 'generic',
|
|
46
|
+
content: [
|
|
47
|
+
{ type: 'text', text: `\`\`\`\n${block.text.replace(/\n+$/, '')}\n\`\`\`` },
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
: { card: 'generic', content: [{ type: 'text', text: block.text }] };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAmCH;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,IAAsB;IAEtB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,SAAS,CAAA;IACxC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,kBAAkB;QACzB,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,GAAG,IAAI,CAAC,aAAa,KAAK,IAAI,GAAG;QAC3C,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAM;gBACZ,IAAI,EACF,IAAI,CAAC,OAAO,KAAK,SAAS;oBACxB,CAAC,CAAC,GAAG,IAAI,OAAO,IAAI,CAAC,aAAa,EAAE;oBACpC,CAAC,CAAC,GAAG,IAAI,SAAS,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,aAAa,EAAE;aAC9D;SACF;KACF,CAAA;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAc,EACd,MAAkB;IAElB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACzE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAA;IAClE,OAAO,MAAM,CAAC,OAAO;QACnB,CAAC,CAAC;YACE,IAAI,EAAE,SAAS;YACf,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,EAAE;aAC5E;SACF;QACH,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,CAAA;AACxE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jianxx/dsh-cc-tool-notebook-edit",
|
|
3
|
+
"description": "NotebookEdit model tool that edits Jupyter notebook (.ipynb) cells over the ctx.fs seam, aligned with Claude Code's NotebookEditTool replace/insert/delete, cell addressing, and read-before-write semantics",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jianxx/dsh-cc-plugins.git",
|
|
8
|
+
"directory": "packages/core/tool-notebook-edit"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"default": "./lib/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./invariant": {
|
|
17
|
+
"types": "./lib/invariant.d.ts",
|
|
18
|
+
"default": "./lib/invariant.js"
|
|
19
|
+
},
|
|
20
|
+
"./render": {
|
|
21
|
+
"types": "./lib/render.d.ts",
|
|
22
|
+
"default": "./lib/render.js"
|
|
23
|
+
},
|
|
24
|
+
"./src/*": "./src/*",
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"lib"
|
|
29
|
+
],
|
|
30
|
+
"license": "Apache-2.0",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@deepseek-ai/dsh-fs": ">=0.1.1-rc.2",
|
|
33
|
+
"@deepseek-ai/dsh-llm": ">=0.1.1-rc.2",
|
|
34
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.1-rc.2",
|
|
35
|
+
"@deepseek-ai/cordis": ">=0.1.1-rc.2",
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
37
|
+
"@jianxx/dsh-cc-tools": "^0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@deepseek-ai/dsh-fs": "link:../../../../deepseek-harness/packages/fs/fs",
|
|
41
|
+
"@deepseek-ai/dsh-fs-local": "link:../../../../deepseek-harness/packages/fs/fs-local",
|
|
42
|
+
"@deepseek-ai/dsh-llm": "link:../../../../deepseek-harness/packages/llm/llm",
|
|
43
|
+
"@deepseek-ai/dsh-invariants": "link:../../../../deepseek-harness/packages/runtime-diagnostics/invariants",
|
|
44
|
+
"@deepseek-ai/cordis": "link:../../../../deepseek-harness/vendor/cordis",
|
|
45
|
+
"@deepseek-ai/schemastery": "link:../../../../deepseek-harness/vendor/schemastery",
|
|
46
|
+
"@deepseek-ai/dsh-system-prompt": "link:../../../../deepseek-harness/packages/core/system-prompt",
|
|
47
|
+
"@jianxx/dsh-cc-tools": "^0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|