@flotiarenor/dsh-tool-text-editor 1.0.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 +202 -0
- package/README.md +177 -0
- package/README.zh.md +167 -0
- package/cordis.patch.yml +22 -0
- package/lib/core.mjs +973 -0
- package/lib/editor.mjs +301 -0
- package/package.json +69 -0
- package/preset/preset.yml +4 -0
- package/scripts/install-preset.mjs +227 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# dsh-tool-text-editor
|
|
2
|
+
|
|
3
|
+
[中文](README.zh.md) | English
|
|
4
|
+
|
|
5
|
+
Model-facing tools for [DeepSeek Harness](https://github.com/deepseek-ai) (dsh) that edit text
|
|
6
|
+
files **byte-faithfully**: `edit_text` and `write_text`.
|
|
7
|
+
|
|
8
|
+
They exist because the built-in tools lose Windows file conventions:
|
|
9
|
+
|
|
10
|
+
| Case (file is UTF-8 **BOM + CRLF**) | built-in `edit` | built-in `write` | this plugin |
|
|
11
|
+
|---|---|---|---|
|
|
12
|
+
| change one line | CRLF kept / **BOM lost** | — | BOM + CRLF kept |
|
|
13
|
+
| full overwrite | — | **BOM lost + CRLF flattened to LF** | BOM + CRLF kept |
|
|
14
|
+
|
|
15
|
+
`@deepseek-ai/dsh-fs-local` has no BOM handling at all (Node's `TextDecoder` strips a leading BOM
|
|
16
|
+
byte by default) and `writeText` does not restore a file's line-ending style.
|
|
17
|
+
|
|
18
|
+
On top of fidelity: **unified diffs** (with a `dry_run` preview), **automatic backups**, an **edit
|
|
19
|
+
ledger**, **`grep` / `lines` anchors** so old text never has to be copied by hand, **ambiguity
|
|
20
|
+
refusal**, and **near-miss candidates** when an anchor does not match.
|
|
21
|
+
|
|
22
|
+
## Implementation and requirements
|
|
23
|
+
|
|
24
|
+
The implementation is **in-process Node** (`lib/core.mjs`): `node:` builtins only, no subprocess, no
|
|
25
|
+
build step, no third-party package.
|
|
26
|
+
|
|
27
|
+
| Requirement | Notes |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Node | **The only dependency** — no interpreter, no external runtime, no process-startup cost per call. |
|
|
30
|
+
|
|
31
|
+
The package installs nothing of its own. Its single `peerDependencies` entry, `@deepseek-ai/dsh-tools`,
|
|
32
|
+
is the host contract — "needs this dsh or newer" — and resolves from the dsh installation rather than
|
|
33
|
+
being installed beside the plugin.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
### Option 1 — preset (recommended, tightly scoped)
|
|
38
|
+
|
|
39
|
+
Only sessions that select this preset see the two tools. Run this from the root of a clone of this
|
|
40
|
+
repository:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
node scripts/install-preset.mjs
|
|
44
|
+
# flags: --id <preset-id> (default texteditor) / --base <shipped-preset> (default standard)
|
|
45
|
+
# / --force / --dry-run / --from <agent.cordis.yml path>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then restart `dsh web` and start a new session on preset `texteditor`; a preset is a session-creation
|
|
49
|
+
fact, so a running session cannot switch to it.
|
|
50
|
+
|
|
51
|
+
### Option 2 — install into the profile (available to every session)
|
|
52
|
+
|
|
53
|
+
`dsh plugin add` accepts several kinds of source, and every one of them works here: the package is
|
|
54
|
+
prebuilt, dependency-free ESM, so there is no `prepare`/`build` step to authorize or run.
|
|
55
|
+
|
|
56
|
+
| Source | Command |
|
|
57
|
+
|---|---|
|
|
58
|
+
| a local checkout | `dsh plugin --profile web add link:<absolute-path-to-this-checkout>` |
|
|
59
|
+
| npm, once published | `dsh plugin --profile web add @flotiarenor/dsh-tool-text-editor` |
|
|
60
|
+
| a packed tarball | `pnpm pack`, then `dsh plugin --profile web add ./<the-file-it-printed>.tgz` |
|
|
61
|
+
| a git repository (tracks the default branch) | `dsh plugin --profile web add github:Flotiarenor/dsh-tool-text-editor` |
|
|
62
|
+
|
|
63
|
+
Verify the layer without starting anything, then restart:
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
dsh --profile web --dump-config # look for the "# == @flotiarenor/dsh-tool-text-editor" layer
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The tool names do not collide with the built-ins, so a host-plane insert is safe. The trade-off is
|
|
70
|
+
that both tools (and their guidance section) show up in **every** session. Uninstall with
|
|
71
|
+
`dsh plugin --profile web remove @flotiarenor/dsh-tool-text-editor`.
|
|
72
|
+
|
|
73
|
+
Both installs may coexist: the preset layer shadows the host layer with an identical definition.
|
|
74
|
+
|
|
75
|
+
## Tools
|
|
76
|
+
|
|
77
|
+
### `edit_text` — targeted replacement
|
|
78
|
+
|
|
79
|
+
`file_path` and `new_text` are required; give **exactly one** anchor: `old_text` (literal, copied from
|
|
80
|
+
`read`), `grep` (regex; the matched line/block including its trailing newline), or `lines` (e.g.
|
|
81
|
+
`"263:270"`). `mode` is `replace` (default) / `after` / `before` / `append` / `prepend`; also `count`
|
|
82
|
+
(require exactly N occurrences and replace all), `nth` (k-th occurrence), `strict`, `dry_run`, `note`.
|
|
83
|
+
`count` and `nth` are mutually exclusive.
|
|
84
|
+
|
|
85
|
+
### `write_text` — create or fully replace a file
|
|
86
|
+
|
|
87
|
+
`file_path` + `content`; creation needs no flag, an overwrite is backed up first, and a brand-new file
|
|
88
|
+
follows the **majority** line-ending style of its siblings (same extension first) with no BOM by
|
|
89
|
+
default.
|
|
90
|
+
|
|
91
|
+
Both **write by default** (like the built-ins); pass `dry_run: true` to preview. The returned text
|
|
92
|
+
always includes the diff.
|
|
93
|
+
|
|
94
|
+
## Deliberate limitations
|
|
95
|
+
|
|
96
|
+
These are design choices, not defects to be fixed; check them against your use case before relying on
|
|
97
|
+
the tools.
|
|
98
|
+
|
|
99
|
+
- **Writes bypass `ctx.fs`.** The file is written by the plugin itself, so the fs-observation policy
|
|
100
|
+
(read-before-write, version freshness), the sandbox, `sandbox_permissions` escalation and Windows
|
|
101
|
+
DACL preservation are all skipped — and the Web UI therefore shows no diff card (the model still
|
|
102
|
+
receives the textual diff).
|
|
103
|
+
- **Line anchors are not content-verified.** `lines` and `before` / `after <line>` locate text by line
|
|
104
|
+
number alone: a wrong number does not fail, it edits somewhere else. When the anchor has to be
|
|
105
|
+
verifiable, use `old_text` or `grep`.
|
|
106
|
+
- **Per-target serialization is per process.** An in-process queue per target plus an atomic write
|
|
107
|
+
keeps parallel tool calls from overwriting each other, but another dsh instance, an editor or any
|
|
108
|
+
other process writing the same file still can, and external changes are not detected.
|
|
109
|
+
- **UTF-8 text only.** Files containing NUL bytes (binary) or invalid UTF-8 are refused, as are paths
|
|
110
|
+
inside `.git/` or `.dsh/` and paths outside the workspace.
|
|
111
|
+
|
|
112
|
+
## Configuration
|
|
113
|
+
|
|
114
|
+
There is no Config schema: the preset row's `config:` mapping is passed through as-is.
|
|
115
|
+
|
|
116
|
+
| Key | Default | Meaning |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `backup` | `true` | copy the previous content into `artifactsDir/backups` before writing |
|
|
119
|
+
| `ledger` | `true` | append a JSONL record to `artifactsDir/edits.log` |
|
|
120
|
+
| `artifactsDir` | `<workspace>/.dsh` | where backups and the ledger live |
|
|
121
|
+
| `newFileBom` | `false` | write a UTF-8 BOM when creating a new file |
|
|
122
|
+
| `context` | `3` | context lines in the unified diff |
|
|
123
|
+
| `root` | `process.cwd()` | fallback workspace when a call has no agent session |
|
|
124
|
+
|
|
125
|
+
`DSH_TEXT_EDITOR_EOL` (`lf` \| `crlf`) overrides the line-ending inference for **new** files.
|
|
126
|
+
|
|
127
|
+
## Self-test and gates
|
|
128
|
+
|
|
129
|
+
```powershell
|
|
130
|
+
# run from the root of a clone of this repository
|
|
131
|
+
node tools/selftest.mjs # 75/75 on Windows + Node 24
|
|
132
|
+
node tools/check-license.mjs # license / dependency / Node-only gate
|
|
133
|
+
node tools/gen-schema.mjs # embedded schemas still match the DSL
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
These three live in the repository only: `tools/` is deliberately outside the `files` whitelist, so
|
|
137
|
+
the published package is just the plugin, its preset installer, the docs and the license.
|
|
138
|
+
|
|
139
|
+
`tools/selftest.mjs` covers BOM/EOL fidelity, `dry_run`, all four anchor kinds, `count`, ambiguity
|
|
140
|
+
refusal, usage errors, binary/invalid-UTF-8 refusal, `.dsh/` and outside-workspace guards, majority
|
|
141
|
+
EOL inference, multi-hunk diffs, end-of-file newline changes and concurrent writes — **plus a
|
|
142
|
+
plugin-layer suite** that drives `apply()` with a fake context and asserts tool registration, the
|
|
143
|
+
guidance section, that every returned value satisfies `OUTPUT_SCHEMA`, the `render()` text, and the
|
|
144
|
+
config plumbing (`root` / `backup` / `ledger` / `newFileBom`).
|
|
145
|
+
|
|
146
|
+
`tools/gen-schema.mjs` needs an installed `@deepseek-ai/dsh-tools`: it looks for one under the dsh
|
|
147
|
+
profile's `node_modules` and under the npm global prefix, and `DSH_TOOLS_ENTRY` overrides that lookup.
|
|
148
|
+
It exits 2 when it cannot find one.
|
|
149
|
+
|
|
150
|
+
## Layout
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
lib/core.mjs # the core: BOM/EOL, anchors, matching, diff, backups, ledger, atomic write, per-target lock
|
|
154
|
+
lib/editor.mjs # the plugin: schemas, validation, tool registration (zero-dep ESM, no build)
|
|
155
|
+
preset/preset.yml # preset name/description, as dsh lists it
|
|
156
|
+
scripts/install-preset.mjs # derives the user preset from the local dsh installation
|
|
157
|
+
cordis.patch.yml # host-plane bundle patch
|
|
158
|
+
tools/selftest.mjs # end-to-end self-test (core + plugin layer)
|
|
159
|
+
tools/check-license.mjs # license / dependency / Node-only hygiene gate
|
|
160
|
+
tools/gen-schema.mjs # authoritative source and checker for the embedded JSON Schemas
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Backups and the ledger use fixed, documented names and fields: one file per edit under
|
|
164
|
+
`.dsh/backups/`, named `<flattened-absolute-path>@<timestamp>`, and one JSON object per line in
|
|
165
|
+
`.dsh/edits.log` (`time`, `id`, `tool`, `file`, `abspath`, `action`, `kinds`, `line_start`,
|
|
166
|
+
`line_end`, `added`, `removed`, `bom`, `eol`, `backup`, `summary`).
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
**Apache-2.0** — see [LICENSE](LICENSE). Copyright 2026 Flotiarenor. The package has **no runtime
|
|
171
|
+
dependencies**, so it carries no third-party license obligations of its own. Three notes:
|
|
172
|
+
|
|
173
|
+
- The embedded JSON Schemas in `lib/editor.mjs` are generated *output* of the `@deepseek-ai/dsh-tools`
|
|
174
|
+
converter (MIT, Copyright (c) 2026 DeepSeek) via `tools/gen-schema.mjs`.
|
|
175
|
+
- The preset composition is **not** part of this package: `scripts/install-preset.mjs` reads the one
|
|
176
|
+
shipped with the user's own dsh installation at install time.
|
|
177
|
+
- Source files carry an `SPDX-License-Identifier` header, so the license is machine-readable per file.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# dsh-tool-text-editor
|
|
2
|
+
|
|
3
|
+
中文 | [English](README.md)
|
|
4
|
+
|
|
5
|
+
[DeepSeek Harness](https://github.com/deepseek-ai)(dsh)的模型工具插件,提供两个**字节保真**的
|
|
6
|
+
文本编辑工具:`edit_text` 与 `write_text`。
|
|
7
|
+
|
|
8
|
+
原生工具在 Windows 上存在两处缺陷:
|
|
9
|
+
|
|
10
|
+
| 场景(文件为 UTF-8 **BOM + CRLF**) | 原生 `edit` | 原生 `write` | 本插件 |
|
|
11
|
+
| ---------------------------------------- | ----------------------------- | ----------------------------------- | ----------------- |
|
|
12
|
+
| 改一行 | CRLF 保住 / **BOM 丢失** | — | BOM + CRLF 都保住 |
|
|
13
|
+
| 整篇覆盖 | — | **BOM 丢失 + CRLF 被拍成 LF** | BOM + CRLF 都保住 |
|
|
14
|
+
|
|
15
|
+
原因在于 `@deepseek-ai/dsh-fs-local` 完全没有 BOM 处理(Node 的 `TextDecoder` 默认吞掉前导
|
|
16
|
+
BOM 字节),且 `writeText` 不按原文件风格还原行尾。
|
|
17
|
+
|
|
18
|
+
在保真之外,本插件还提供:**统一 diff**(可用 dry-run 预览)、**写入前自动备份**、**编辑台账**、
|
|
19
|
+
**`grep` / `lines` 锚点**(无需人工誊抄原文)、**歧义时拒绝写入**,以及**最接近候选**提示。
|
|
20
|
+
|
|
21
|
+
## 实现与依赖
|
|
22
|
+
|
|
23
|
+
实现是**进程内 Node**(`lib/core.mjs`):只用 `node:` 内置模块,不启动任何子进程,无构建步骤、
|
|
24
|
+
无第三方依赖。
|
|
25
|
+
|
|
26
|
+
| 依赖 | 说明 |
|
|
27
|
+
| ---- | ------------------------------------------------------------------------------ |
|
|
28
|
+
| Node | 插件唯一的依赖。不启动解释器、不引入外部运行时;每次调用均无进程启动开销。 |
|
|
29
|
+
|
|
30
|
+
## 安装
|
|
31
|
+
|
|
32
|
+
### 方式一:preset(推荐,作用域最小)
|
|
33
|
+
|
|
34
|
+
仅选定该 preset 的会话可见这两个工具,其它项目与会话的工具表不受影响。**在本仓库根目录**执行:
|
|
35
|
+
|
|
36
|
+
```powershell
|
|
37
|
+
node scripts/install-preset.mjs
|
|
38
|
+
# 可选:--id <preset-id>(默认 texteditor)/ --base <自带 preset>(默认 standard)
|
|
39
|
+
# / --force / --dry-run / --from <agent.cordis.yml 路径>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
随后重启 `dsh web`,新建会话时选择 preset `texteditor`;preset 属会话创建期事实,已在运行的会话无法切换。
|
|
43
|
+
|
|
44
|
+
### 方式二:安装到 profile(所有会话可用)
|
|
45
|
+
|
|
46
|
+
`dsh plugin add` 支持多种来源,**以下来源均受支持**:本包为预构建的零依赖 ESM,无
|
|
47
|
+
`prepare` / `build` 步骤,因此既无需用户授权构建,安装时也不会执行任何构建脚本。
|
|
48
|
+
|
|
49
|
+
| 来源 | 命令 |
|
|
50
|
+
| -------------- | --------------------------------------------------------------------------- |
|
|
51
|
+
| 本地仓库 | `dsh plugin --profile web add link:<本仓库的绝对路径>` |
|
|
52
|
+
| npm(发布后) | `dsh plugin --profile web add @flotiarenor/dsh-tool-text-editor` |
|
|
53
|
+
| 打包的 tarball | 先 `pnpm pack`,再 `dsh plugin --profile web add ./<pnpm pack 输出的文件名>.tgz` |
|
|
54
|
+
| git 仓库(跟随默认分支) | `dsh plugin --profile web add github:Flotiarenor/dsh-tool-text-editor` |
|
|
55
|
+
|
|
56
|
+
可在启动前用以下命令确认该层是否生效:
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
dsh --profile web --dump-config # 应当能看到 "# == @flotiarenor/dsh-tool-text-editor"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`edit_text` / `write_text` 与原生工具不重名,插入宿主组合不会产生注册冲突。
|
|
63
|
+
|
|
64
|
+
卸载:`dsh plugin --profile web remove @flotiarenor/dsh-tool-text-editor`。
|
|
65
|
+
|
|
66
|
+
两种方式可以并存:同名时 preset 层的注册会遮蔽宿主层的注册,二者定义相同、行为一致。
|
|
67
|
+
|
|
68
|
+
## 工具契约
|
|
69
|
+
|
|
70
|
+
### `edit_text` —— 局部替换
|
|
71
|
+
|
|
72
|
+
| 参数 | 必填 | 说明 |
|
|
73
|
+
| ------------- | ---- | ------------------------------------------------------------------------ |
|
|
74
|
+
| `file_path` | ✅ | 目标文件;相对路径按会话工作区解析。 |
|
|
75
|
+
| `new_text` | ✅ | 替换或插入的内容。 |
|
|
76
|
+
| `old_text` | - | 字面量锚点(可直接复制 `read` 的输出)。 |
|
|
77
|
+
| `grep` | - | 正则锚点:命中的行或行块作为锚点(含行尾换行符)。 |
|
|
78
|
+
| `lines` | - | 行号锚点,如 `"263:270"` 或 `"120"`。 |
|
|
79
|
+
| `mode` | - | `replace`(默认)/ `after` / `before` / `append` / `prepend`。 |
|
|
80
|
+
| `count` | - | 要求恰好 N 处命中并全部替换(不符则拒绝写入)。 |
|
|
81
|
+
| `nth` | - | 只替换第 k 处(1-based)。 |
|
|
82
|
+
| `strict` | - | 禁用宽松匹配(只接受精确匹配)。 |
|
|
83
|
+
| `dry_run` | - | 只输出 diff,不写入文件。 |
|
|
84
|
+
| `note` | - | 一行说明,记入编辑台账。 |
|
|
85
|
+
|
|
86
|
+
`old_text` / `grep` / `lines` **必须且只能提供一个**;`append` / `prepend` 不接受锚点,`after` / `before`
|
|
87
|
+
只能与 `grep` / `lines` 搭配。数量不符将拒绝写入。`count` 与 `nth` 互斥。
|
|
88
|
+
|
|
89
|
+
### `write_text` —— 整文件新建/覆盖
|
|
90
|
+
|
|
91
|
+
`file_path` + `content`;目标不存在时自动新建,覆盖前先备份。新建文件的行尾风格取自
|
|
92
|
+
同目录的多数派(同扩展名优先),默认不写 BOM。
|
|
93
|
+
|
|
94
|
+
## 已知限制
|
|
95
|
+
|
|
96
|
+
以下均为有意的设计取舍,而非缺陷;采用前请对照自身场景确认。
|
|
97
|
+
|
|
98
|
+
- **写入不经由 `ctx.fs`。** 文件由本插件直接写入,因此不经过 fs 观察策略(先读后写、版本新鲜度校验)、
|
|
99
|
+
沙箱与 `sandbox_permissions` 审批升权,也不保留 Windows DACL。对应地,Web UI 不会生成 diff 卡片
|
|
100
|
+
(模型仍可读到文本 diff)。
|
|
101
|
+
- **行号锚点不做内容校验。** `lines` 与 `before` / `after <行号>` 仅按行号定位:行号有误不会报错,
|
|
102
|
+
改动会落在非预期位置;定位需要可校验时,请改用 `old_text` 或 `grep`。
|
|
103
|
+
- **同目标串行仅限本进程。** 进程内按目标路径排队,并配合原子写,故并行的工具调用不会相互覆盖;
|
|
104
|
+
但另一个 dsh 实例、编辑器或其它进程同时修改同一文件时,仍可能相互覆盖,本插件也不检测外部改动。
|
|
105
|
+
- **仅处理 UTF-8 文本。** 含 NUL 字节的二进制文件与非法 UTF-8 文件一律拒绝;`.git/`、`.dsh/` 内部
|
|
106
|
+
以及工作区之外的路径一律拒绝写入。
|
|
107
|
+
|
|
108
|
+
## 配置
|
|
109
|
+
|
|
110
|
+
本插件不定义 Config schema:preset 行的 `config:` 字段原样透传。
|
|
111
|
+
|
|
112
|
+
| 键 | 默认 | 含义 |
|
|
113
|
+
| ---------------- | ----------------- | ------------------------------------------------ |
|
|
114
|
+
| `backup` | `true` | 写入前把原内容复制到 `artifactsDir/backups` |
|
|
115
|
+
| `ledger` | `true` | 往 `artifactsDir/edits.log` 追加一条 JSONL 记录 |
|
|
116
|
+
| `artifactsDir` | `<工作区>/.dsh` | 备份与台账所在目录 |
|
|
117
|
+
| `newFileBom` | `false` | 新建文件时是否写 UTF-8 BOM |
|
|
118
|
+
| `context` | `3` | unified diff 的上下文行数 |
|
|
119
|
+
| `root` | `process.cwd()` | 无 agent 会话时的回退工作区 |
|
|
120
|
+
|
|
121
|
+
环境变量 `DSH_TEXT_EDITOR_EOL`(`lf` \| `crlf`)可覆盖**新建文件**的行尾推断。
|
|
122
|
+
|
|
123
|
+
## 自测与门禁
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
# 在本仓库根目录执行
|
|
127
|
+
node tools/selftest.mjs # Windows + Node 24 参考结果 75/75
|
|
128
|
+
node tools/check-license.mjs # 许可证 / 依赖 / 纯 Node 门禁
|
|
129
|
+
node tools/gen-schema.mjs # 内嵌 schema 是否仍与作者 DSL 一致
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`tools/selftest.mjs` 覆盖:BOM 与行尾保真、`dry_run`、四种锚点、`count`、歧义时拒绝写入、用法错误、
|
|
133
|
+
二进制与非法 UTF-8、路径护栏(`.dsh/`、工作区之外)、行尾多数派推断、跨多个 hunk、末尾换行差异、
|
|
134
|
+
并发写入不产生半截文件;**并含一层插件层断言**:以模拟 ctx 驱动 `apply()`,验证工具注册、引导段
|
|
135
|
+
身份、每个返回值均满足 `OUTPUT_SCHEMA`、`render()` 输出,以及 config 透传(`root` / `backup` /
|
|
136
|
+
`ledger` / `newFileBom`)。
|
|
137
|
+
|
|
138
|
+
`tools/gen-schema.mjs` 需要一份装有 `@deepseek-ai/dsh-tools` 的 dsh:它会在 dsh profile 的
|
|
139
|
+
`node_modules` 与 npm 全局目录中自动查找,也可用 `DSH_TOOLS_ENTRY` 显式指定;找不到入口时退出码为 2。
|
|
140
|
+
|
|
141
|
+
## 目录结构
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
lib/core.mjs # 编辑核心:BOM/行尾、锚点、匹配、diff、备份、台账、原子写、同目标串行
|
|
145
|
+
lib/editor.mjs # 插件本体:schema、参数校验、工具注册(零依赖 ESM,无构建)
|
|
146
|
+
preset/preset.yml # preset 的名字/描述(dsh 列表里显示的内容)
|
|
147
|
+
scripts/install-preset.mjs # 从本机 dsh 派生用户 preset
|
|
148
|
+
cordis.patch.yml # 宿主平面安装用的 bundle patch
|
|
149
|
+
tools/selftest.mjs # 端到端自测(核心 + 插件层)
|
|
150
|
+
tools/check-license.mjs # 许可证 / 依赖 / 纯 Node 卫生门禁
|
|
151
|
+
tools/gen-schema.mjs # 内嵌 schema 的权威来源与校验器
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
备份与台账采用固定的命名与字段:每次编辑在 `.dsh/backups/` 下留存一个文件,命名为
|
|
155
|
+
`<绝对路径扁平化>@<时间戳>`;`.dsh/edits.log` 每行一个 JSON 对象(`time`、`id`、`tool`、`file`、
|
|
156
|
+
`abspath`、`action`、`kinds`、`line_start`、`line_end`、`added`、`removed`、`bom`、`eol`、
|
|
157
|
+
`backup`、`summary`)。
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
**Apache-2.0**,见 [LICENSE](LICENSE)。Copyright 2026 Flotiarenor。本包**零运行时依赖**,因此不承担
|
|
162
|
+
任何第三方许可证义务。
|
|
163
|
+
|
|
164
|
+
- `lib/editor.mjs` 内嵌的 JSON Schema 是 `@deepseek-ai/dsh-tools`(MIT,Copyright (c) 2026 DeepSeek)
|
|
165
|
+
转换器的**生成产物**(由 `tools/gen-schema.mjs` 离线生成)。
|
|
166
|
+
- preset 组合**不在本包内**:`scripts/install-preset.mjs` 在安装时读取使用者所装 dsh 自带的组合。
|
|
167
|
+
- 源文件均带 `SPDX-License-Identifier` 头,许可证**逐文件机器可读**。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Flotiarenor
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
# dsh-tool-text-editor bundle patch:把 edit_text / write_text 两个工具行插进 profile 的宿主组合,
|
|
4
|
+
# 从而对**所有**会话可见(不限于某一个 preset)。
|
|
5
|
+
#
|
|
6
|
+
# dsh plugin --profile web add link:<本仓库的绝对路径>
|
|
7
|
+
# # 然后重启 dsh web
|
|
8
|
+
#
|
|
9
|
+
# 为什么可以走宿主平面:本插件注册的是 `edit_text` / `write_text` 两个**不与原生工具重名**的工具,
|
|
10
|
+
# 不存在同名注册冲突;它也消费宿主服务(tools / systemPrompt)、不发布任何服务,实现是纯 Node
|
|
11
|
+
# (进程内、零依赖、不启动外部进程),因此宿主平面插入没有额外依赖。
|
|
12
|
+
#
|
|
13
|
+
# 与 preset 安装方式的区别:
|
|
14
|
+
# * 这里(宿主层)= 每个会话都有这两个工具,任何 preset 都能用;
|
|
15
|
+
# * `scripts/install-preset.mjs`(preset 层)= 只有选了这个 preset 的会话有,
|
|
16
|
+
# 其它项目/会话的工具表保持干净。
|
|
17
|
+
# 两种方式可以同时存在:preset 层的注册在同名时遮蔽宿主层的,定义相同、行为一致。
|
|
18
|
+
#
|
|
19
|
+
# 卸载:`dsh plugin --profile web remove @flotiarenor/dsh-tool-text-editor`,再重启 dsh web。
|
|
20
|
+
- insert:
|
|
21
|
+
- id: tool-text-editor
|
|
22
|
+
name: '@flotiarenor/dsh-tool-text-editor'
|