@x1a0f3n9/dsh-tool-fs 0.1.5-rc.4 → 0.1.7-rc.2

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/README.i18n.yaml CHANGED
@@ -1,6 +1,142 @@
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:
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/fs/tool-fs/README.md
5
- README.md: 9d338a6227b7f02ef43fcbe97900b166cb722817
6
- README.zh.md: 0c34f840ad449847a649df8abdadec01f772f37b
5
+ /:
6
+ en: 52b93805befe8077
7
+ zh: 9786e662303f0e2f
8
+ /x1a0f3n9-dsh-tool-fs:
9
+ en: c53838f9abadf120
10
+ zh: e65d8cbc2c3a6691
11
+ /x1a0f3n9-dsh-tool-fs/summary:
12
+ en: c8f608c85c8d6626
13
+ zh: 4133a867517baca5
14
+ /x1a0f3n9-dsh-tool-fs/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /x1a0f3n9-dsh-tool-fs/use-this-package:
18
+ en: 8fd62afcdabe55d4
19
+ zh: 9bdcae272575be8c
20
+ /x1a0f3n9-dsh-tool-fs/use-this-package/minimal-composition:
21
+ en: eb21d89a3ecbd2d4
22
+ zh: f05ccbb1e7515f32
23
+ /x1a0f3n9-dsh-tool-fs/use-this-package/the-tools:
24
+ en: e70e0164411016b5
25
+ zh: 3fa6caa8f3d70552
26
+ /x1a0f3n9-dsh-tool-fs/use-this-package/configuration:
27
+ en: 7eb705e3b132fcba
28
+ zh: 883207b13d381f9e
29
+ /x1a0f3n9-dsh-tool-fs/use-this-package/policy-and-sandbox-behavior:
30
+ en: 53d4ac2db089ef39
31
+ zh: 214ea051ed56f500
32
+ /x1a0f3n9-dsh-tool-fs/use-this-package/failures-and-recovery:
33
+ en: 962b09e21305a04b
34
+ zh: 382d2b8cce217569
35
+ /x1a0f3n9-dsh-tool-fs/understand-the-implementation:
36
+ en: e30f7038c28e93f8
37
+ zh: a3868dbd90367b13
38
+ /x1a0f3n9-dsh-tool-fs/understand-the-implementation/design-concept:
39
+ en: 454b5291db9ba847
40
+ zh: 9fe7081dfb416c93
41
+ /x1a0f3n9-dsh-tool-fs/understand-the-implementation/source-map:
42
+ en: e8220ed04480aadc
43
+ zh: 1cc5f9a505953102
44
+ /x1a0f3n9-dsh-tool-fs/understand-the-implementation/per-tool-flow:
45
+ en: d741e01be9fddb76
46
+ zh: 7aa1d7ca719a4aac
47
+ /x1a0f3n9-dsh-tool-fs/understand-the-implementation/observation-and-concurrency:
48
+ en: 665d437dc35bf05f
49
+ zh: 8004334d6a3246b4
50
+ /x1a0f3n9-dsh-tool-fs/further-exploration:
51
+ en: 485bc55482f576bc
52
+ zh: fc5fa55b41098bd0
53
+ /x1a0f3n9-dsh-tool-fs/model-experience:
54
+ en: 215e7ba838619b7b
55
+ zh: a311da3843709f90
56
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt:
57
+ en: cf78b9dd5596495f
58
+ zh: bd44560f8c2435e8
59
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt/what-the-model-sees:
60
+ en: b09ee4be5e522c29
61
+ zh: ffd30b57d7fc96e8
62
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt/what-the-model-sees/read-guidance:
63
+ en: f8961df3f3a6a671
64
+ zh: 85c7bd418b40b089
65
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt/what-the-model-sees/write-guidance:
66
+ en: 7e34af48c583f210
67
+ zh: e39ad6823689c665
68
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt/what-the-model-sees/edit-guidance:
69
+ en: 28f07502a411303b
70
+ zh: 437897dc99fc4360
71
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt/token-effect:
72
+ en: 7a44cabe439a879a
73
+ zh: 1adf9bf3de7658b6
74
+ /x1a0f3n9-dsh-tool-fs/model-experience/system-prompt/kv-cache-effect:
75
+ en: 4f1b5639f91405a8
76
+ zh: f90ffe077a2e52a3
77
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-schemas:
78
+ en: 3039bc5e7a796b21
79
+ zh: 6b7a9eaf69c9ea53
80
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-schemas/what-the-model-sees:
81
+ en: 6f2a072c474e39e7
82
+ zh: dbee4c2b206bf68f
83
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-schemas/token-effect:
84
+ en: 68517f7d65ef7e9d
85
+ zh: 544a4b81e8ea16c0
86
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-schemas/kv-cache-effect:
87
+ en: 1e6d3da20012762d
88
+ zh: 482f8bc569b8b490
89
+ /x1a0f3n9-dsh-tool-fs/model-experience/read-result:
90
+ en: ebd38733c0490428
91
+ zh: b45101c325efe784
92
+ /x1a0f3n9-dsh-tool-fs/model-experience/read-result/what-the-model-sees:
93
+ en: 06e250bd8788248a
94
+ zh: e00d36b48068909a
95
+ /x1a0f3n9-dsh-tool-fs/model-experience/read-result/token-effect:
96
+ en: 4269b4368622ec2f
97
+ zh: f4df80c1fb6972f1
98
+ /x1a0f3n9-dsh-tool-fs/model-experience/read-result/kv-cache-effect:
99
+ en: f5d5f97a153f7c70
100
+ zh: 3d6bc05b9eadfbc2
101
+ /x1a0f3n9-dsh-tool-fs/model-experience/image-read-result:
102
+ en: 9dac44e6d0b9d8de
103
+ zh: 7bc4dc1c5df39eaf
104
+ /x1a0f3n9-dsh-tool-fs/model-experience/image-read-result/what-the-model-sees:
105
+ en: 9252b6d73776c5ff
106
+ zh: d6e433450989399e
107
+ /x1a0f3n9-dsh-tool-fs/model-experience/image-read-result/token-effect:
108
+ en: 68bd5255f1c9ca48
109
+ zh: 0474213531c1aae3
110
+ /x1a0f3n9-dsh-tool-fs/model-experience/image-read-result/kv-cache-effect:
111
+ en: f5d5f97a153f7c70
112
+ zh: 48e202e396bc5fcc
113
+ /x1a0f3n9-dsh-tool-fs/model-experience/write-and-edit-results:
114
+ en: 1b3b6cfa3605d0b4
115
+ zh: 78e37c2aa4f76d54
116
+ /x1a0f3n9-dsh-tool-fs/model-experience/write-and-edit-results/what-the-model-sees:
117
+ en: bb2a92e0e43e3273
118
+ zh: a744395576b48a93
119
+ /x1a0f3n9-dsh-tool-fs/model-experience/write-and-edit-results/token-effect:
120
+ en: fb4d0b17a99f49cf
121
+ zh: 17460a2a5d72f6cc
122
+ /x1a0f3n9-dsh-tool-fs/model-experience/write-and-edit-results/kv-cache-effect:
123
+ en: f5d5f97a153f7c70
124
+ zh: 3d6bc05b9eadfbc2
125
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-errors:
126
+ en: 2d4f2c8605eccab3
127
+ zh: 67f1f0218f73c6bf
128
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-errors/what-the-model-sees:
129
+ en: 4e8fac7bac8ced61
130
+ zh: 7ba921dbcddc9f97
131
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-errors/token-effect:
132
+ en: 681cadd5be7b8ac1
133
+ zh: 65e483f62fb819d9
134
+ /x1a0f3n9-dsh-tool-fs/model-experience/tool-errors/kv-cache-effect:
135
+ en: f5d5f97a153f7c70
136
+ zh: 3d6bc05b9eadfbc2
137
+ /x1a0f3n9-dsh-tool-fs/known-limitations-and-deferred-work:
138
+ en: ce7a91d41d2f7597
139
+ zh: d4a3c905e124fd7f
140
+ /x1a0f3n9-dsh-tool-fs/known-limitations-and-deferred-work/dev-note:
141
+ en: 0c4d0c465413d319
142
+ zh: a20efeb10121b927
package/README.md CHANGED
@@ -67,7 +67,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
67
67
 
68
68
  Path authorization for `read` and `read_image` belongs entirely to `ctx.fs`; media-type declarations and file signatures only decide whether `read_image` accepts the bytes returned by that backend.
69
69
 
70
- With the policy plugin mounted, `write` and `edit` obtain their guard from the `fs/*` intent slots, so an unread target or a stale observation fails with `FS_NOT_OBSERVED` or `FS_STALE_VERSION` and a recovery instruction. Under a confining backend (`fs-sandbox`), `write`/`edit` additionally advertise `sandbox_permissions` and `justification`; a denied mutation returns the `[sandbox: file access denied under <mode> mode]` marker with the same-turn escalation hint, and an approved retry may stamp a strictly wider mode for that one call.
70
+ With the policy plugin mounted, `write` and `edit` obtain their guard from the `fs/*` intent slots, so an unread target or a stale observation fails with `FS_NOT_OBSERVED` or `FS_STALE_VERSION` and a recovery instruction. Under a confining backend (`fs-sandbox`), `write`/`edit` additionally advertise `sandbox_permissions` and `justification`; a denied mutation returns the `[sandbox: file access denied under <mode> mode]` marker with the same-turn escalation hint, and an approved retry may stamp a strictly wider mode for that one call. The justification asks the model to use the language of the current user request.
71
71
 
72
72
  ### Failures and recovery
73
73
 
@@ -138,19 +138,19 @@ At assembly time, each guidance section checks `ctx.tools.get(name, scope)` and
138
138
  ##### Read guidance
139
139
 
140
140
  ```markdown
141
- Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
141
+ Use the read tool — not shell commands like cat — to inspect text files. Use offset and limit to continue reading large files.
142
142
  ```
143
143
 
144
144
  ##### Write guidance
145
145
 
146
146
  ```markdown
147
- Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-observation-policy requires it) and prefer edit for targeted changes.
147
+ Read an existing file before overwriting it with write (the default fs-observation-policy requires it) and prefer edit for targeted changes.
148
148
  ```
149
149
 
150
150
  ##### Edit guidance
151
151
 
152
152
  ```markdown
153
- Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-observation-policy requires it), unless you just created or edited it in this session.
153
+ Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.
154
154
  ```
155
155
 
156
156
  #### Token effect
package/README.zh.md CHANGED
@@ -67,7 +67,7 @@ kind: "package-reference"
67
67
 
68
68
  `read` 与 `read_image` 的路径授权完全由 `ctx.fs` 负责;媒体类型声明和文件签名只决定 `read_image` 是否接受该后端返回的字节。
69
69
 
70
- 挂载策略插件后,`write` 与 `edit` 从 `fs/*` 意图槽位取得防护,因此未读目标或陈旧观察会以 `FS_NOT_OBSERVED` 或 `FS_STALE_VERSION` 及恢复指令失败。使用施加沙箱限制的后端(`fs-sandbox`)时,`write`/`edit` 还会公开 `sandbox_permissions` 与 `justification`;被拒绝的变更返回 `[sandbox: file access denied under <mode> mode]` 标记与同轮次升级提示,获批的重试可以在该次调用中加盖严格更宽的模式。
70
+ 挂载策略插件后,`write` 与 `edit` 从 `fs/*` 意图槽位取得防护,因此未读目标或陈旧观察会以 `FS_NOT_OBSERVED` 或 `FS_STALE_VERSION` 及恢复指令失败。使用施加沙箱限制的后端(`fs-sandbox`)时,`write`/`edit` 还会公开 `sandbox_permissions` 与 `justification`;被拒绝的变更返回 `[sandbox: file access denied under <mode> mode]` 标记与同轮次升级提示,获批的重试可以在该次调用中加盖严格更宽的模式。理由字段提示模型使用用户当前提问的语言。
71
71
 
72
72
  ### 失败与恢复
73
73
 
@@ -138,19 +138,19 @@ kind: "package-reference"
138
138
  ##### Read 指导
139
139
 
140
140
  ```markdown
141
- Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
141
+ Use the read tool — not shell commands like cat — to inspect text files. Use offset and limit to continue reading large files.
142
142
  ```
143
143
 
144
144
  ##### Write 指导
145
145
 
146
146
  ```markdown
147
- Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-observation-policy requires it) and prefer edit for targeted changes.
147
+ Read an existing file before overwriting it with write (the default fs-observation-policy requires it) and prefer edit for targeted changes.
148
148
  ```
149
149
 
150
150
  ##### Edit 指导
151
151
 
152
152
  ```markdown
153
- Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-observation-policy requires it), unless you just created or edited it in this session.
153
+ Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session.
154
154
  ```
155
155
 
156
156
  #### Token 影响
package/lib/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import { defineTool } from "@x1a0f3n9/dsh-tools";
3
3
  import { FsError } from "@x1a0f3n9/dsh-fs";
4
- import { ESCALATION_TARGETS, approveEscalation, canonicalPath, escalationHintMarker, sandboxDenialMarker, validateEscalationArgs } from "@x1a0f3n9/dsh-sandbox";
4
+ import { readLangHintForPath as langFromPath } from "@x1a0f3n9/dsh-util-code-language";
5
5
  import { structuredPatch } from "diff";
6
6
  import { basename, extname } from "node:path";
7
7
  import { AttachmentError, AttachmentId } from "@x1a0f3n9/dsh-attachment";
8
+ import { ESCALATION_TARGETS, approveEscalation, escalationHintMarker, sandboxDenialMarker, sandboxPermissionsDescription, validateEscalationArgs } from "@x1a0f3n9/dsh-sandbox";
8
9
  //#region lib/types/read-render.js
9
10
  /**
10
11
  * Pure read presentation: turn provider-decoded text into a bounded, line-numbered window and
@@ -110,71 +111,6 @@ ${outcome.lines.length > 0 ? `${outcome.lines.map((line) => `${line.number}: ${l
110
111
  </content>`;
111
112
  }
112
113
  /**
113
- * Lowercased file-extension to syntax-highlighting language hint. Keys are the
114
- * extension without its dot; a UI treats an absent key as plain text. The map is
115
- * intentionally small — common source, config, and markup extensions a
116
- * line-numbered code view benefits from highlighting — not an exhaustive registry.
117
- */
118
- const LANG_BY_EXTENSION = {
119
- ts: "ts",
120
- tsx: "tsx",
121
- mts: "ts",
122
- cts: "ts",
123
- js: "js",
124
- jsx: "jsx",
125
- mjs: "js",
126
- cjs: "js",
127
- json: "json",
128
- jsonc: "json",
129
- py: "py",
130
- rb: "rb",
131
- go: "go",
132
- rs: "rs",
133
- java: "java",
134
- c: "c",
135
- h: "c",
136
- cc: "cpp",
137
- cpp: "cpp",
138
- hpp: "cpp",
139
- cxx: "cpp",
140
- cs: "cs",
141
- kt: "kotlin",
142
- swift: "swift",
143
- php: "php",
144
- sh: "sh",
145
- bash: "sh",
146
- zsh: "sh",
147
- yaml: "yaml",
148
- yml: "yaml",
149
- toml: "toml",
150
- ini: "ini",
151
- md: "md",
152
- markdown: "md",
153
- mdx: "mdx",
154
- html: "html",
155
- htm: "html",
156
- css: "css",
157
- scss: "scss",
158
- less: "less",
159
- sql: "sql",
160
- xml: "xml",
161
- lua: "lua"
162
- };
163
- /**
164
- * Derive a syntax-highlighting language hint from a read path's file extension.
165
- * Pure and case-insensitive on the extension; a dotfile with no extension
166
- * (`.gitignore`) and an unknown extension both yield `undefined`.
167
- * @param path - the model-facing path the read reported.
168
- * @returns the language hint for {@link LANG_BY_EXTENSION}, or `undefined` when the extension maps to none.
169
- */
170
- function langFromPath(path) {
171
- const base = path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1);
172
- const dot = base.lastIndexOf(".");
173
- if (dot <= 0) return void 0;
174
- const ext = base.slice(dot + 1).toLowerCase();
175
- return Object.hasOwn(LANG_BY_EXTENSION, ext) ? LANG_BY_EXTENSION[ext] : void 0;
176
- }
177
- /**
178
114
  * Whether `value` is a valid {@link FileTextLine} (defensive narrowing from
179
115
  * opaque `meta`). `number` must be a 1-based integer line number, since a card
180
116
  * rendered from a zero, fractional, or non-finite line number would violate the
@@ -229,28 +165,22 @@ function readMetaFromMeta(meta) {
229
165
  * `process.cwd()` at the tool boundary.
230
166
  * @module @x1a0f3n9/dsh-tool-fs/session-cwd
231
167
  */
232
- const PARENT_PATH_SEGMENT = /(?:^|[\\/])\.\.(?:[\\/]|$)/;
233
168
  /**
234
169
  * The session workspace cwd for this call, or `undefined` when none applies.
235
170
  * @param exec - the tool-execution context; only its optional `agent` is read.
236
- * @param requestedPath - the path the provider will resolve; parent traversal
237
- * makes a symlinked cwd's filesystem identity observable.
238
171
  * @returns the calling agent's session cwd, or undefined for a non-agent caller (the backend then applies its own default).
239
172
  */
240
- function sessionCwd(exec, requestedPath) {
241
- const cwd = exec.agent?.session.header.cwd;
242
- if (cwd === void 0 || !PARENT_PATH_SEGMENT.test(cwd) && !PARENT_PATH_SEGMENT.test(requestedPath)) return cwd;
243
- return canonicalPath(cwd);
173
+ function sessionCwd(exec) {
174
+ return exec.agent?.session.header.cwd;
244
175
  }
245
176
  /**
246
177
  * Resolution options shared by all model-facing filesystem tools.
247
178
  * @param exec - the tool-execution context supplying session cwd and cancellation.
248
- * @param requestedPath - the path the provider will resolve.
249
179
  * @param policyWorkspaceRoot - resolved per-call root, when a mutation carries sandbox policy.
250
180
  * @returns provider resolution options for the current tool call.
251
181
  */
252
- function sessionResolveOptions(exec, requestedPath, policyWorkspaceRoot) {
253
- const cwd = policyWorkspaceRoot ?? sessionCwd(exec, requestedPath);
182
+ function sessionResolveOptions(exec, policyWorkspaceRoot) {
183
+ const cwd = policyWorkspaceRoot ?? sessionCwd(exec);
254
184
  return {
255
185
  ...cwd !== void 0 ? { cwd } : {},
256
186
  signal: exec.signal
@@ -270,7 +200,7 @@ function sessionResolveOptions(exec, requestedPath, policyWorkspaceRoot) {
270
200
  * @returns the resolved target and its single stat result.
271
201
  */
272
202
  async function resolveRegularReadTarget(ctx, exec, requestedPath) {
273
- const target = await ctx.fs.resolve(requestedPath, sessionResolveOptions(exec, requestedPath));
203
+ const target = await ctx.fs.resolve(requestedPath, sessionResolveOptions(exec));
274
204
  const info = await ctx.fs.stat(target, exec.signal);
275
205
  if (info === void 0) {
276
206
  ctx.emit("fs/observed", target, { kind: "absent" }, exec);
@@ -326,7 +256,7 @@ function applyReadTool(ctx, caps) {
326
256
  ctx.systemPrompt.section({
327
257
  name: "tool:read",
328
258
  order: ctx.systemPrompt.getSectionOrder("TOOL_READ"),
329
- text: ({ scope }) => ctx.tools.get("read", scope) === void 0 ? "" : "Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files."
259
+ text: ({ scope }) => ctx.tools.get("read", scope) === void 0 ? "" : "Use the read tool — not shell commands like cat — to inspect text files. Use offset and limit to continue reading large files."
330
260
  });
331
261
  ctx.tools.register(defineTool({
332
262
  name: "read",
@@ -591,7 +521,7 @@ function applyWriteTool(ctx, sandbox) {
591
521
  ctx.systemPrompt.section({
592
522
  name: "tool:write",
593
523
  order: ctx.systemPrompt.getSectionOrder("TOOL_WRITE"),
594
- text: ({ scope }) => ctx.tools.get("write", scope) === void 0 ? "" : "Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-observation-policy requires it)" + (ctx.tools.get("edit", scope) === void 0 ? "" : " and prefer edit for targeted changes") + "."
524
+ text: ({ scope }) => ctx.tools.get("write", scope) === void 0 ? "" : "Read an existing file before overwriting it with write (the default fs-observation-policy requires it)" + (ctx.tools.get("edit", scope) === void 0 ? "" : " and prefer edit for targeted changes") + "."
595
525
  });
596
526
  ctx.tools.register(defineTool({
597
527
  name: "write",
@@ -637,16 +567,19 @@ function applyWriteTool(ctx, sandbox) {
637
567
  type: "text",
638
568
  text: formatWriteOutput(value.path, value)
639
569
  }],
640
- presentationMeta: (args, value) => ({ diffs: value.before === null ? [] : computeHunkDiffs(args.file_path, value.before, value.after).map(({ path, oldText, newText }) => ({
641
- path,
642
- oldText,
643
- newText
644
- })) })
570
+ presentationMeta: (args, value) => ({
571
+ operation: value.operation,
572
+ diffs: value.before === null ? [] : computeHunkDiffs(args.file_path, value.before, value.after).map(({ path, oldText, newText }) => ({
573
+ path,
574
+ oldText,
575
+ newText
576
+ }))
577
+ })
645
578
  },
646
579
  async execute(args, exec) {
647
580
  const input = parseWriteArgs(args);
648
581
  const sandboxPolicy = await sandbox.resolvePolicy("write", args, exec);
649
- const target = await ctx.fs.resolve(input.filePath, sessionResolveOptions(exec, input.filePath, sandboxPolicy?.workspaceRoot));
582
+ const target = await ctx.fs.resolve(input.filePath, sessionResolveOptions(exec, sandboxPolicy?.workspaceRoot));
650
583
  const intent = await ctx.waterfall("fs/write-intent", target, exec, () => void 0);
651
584
  let outcome;
652
585
  try {
@@ -736,7 +669,7 @@ function applyEditTool(ctx, sandbox) {
736
669
  ctx.systemPrompt.section({
737
670
  name: "tool:edit",
738
671
  order: ctx.systemPrompt.getSectionOrder("TOOL_EDIT"),
739
- text: ({ scope }) => ctx.tools.get("edit", scope) === void 0 ? "" : "Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-observation-policy requires it), unless you just created or edited it in this session."
672
+ text: ({ scope }) => ctx.tools.get("edit", scope) === void 0 ? "" : "Read a file before editing it (the default fs-observation-policy requires it), unless you just created or edited it in this session."
740
673
  });
741
674
  ctx.tools.register(defineTool({
742
675
  name: "edit",
@@ -750,7 +683,7 @@ function applyEditTool(ctx, sandbox) {
750
683
  old_string: {
751
684
  type: "string",
752
685
  required: true,
753
- description: "Literal text to replace. Must match exactly."
686
+ description: "Literal text to replace."
754
687
  },
755
688
  new_string: {
756
689
  type: "string",
@@ -795,7 +728,7 @@ function applyEditTool(ctx, sandbox) {
795
728
  async execute(args, exec) {
796
729
  const input = parseEditArgs(args);
797
730
  const sandboxPolicy = await sandbox.resolvePolicy("edit", args, exec);
798
- const target = await ctx.fs.resolve(input.filePath, sessionResolveOptions(exec, input.filePath, sandboxPolicy?.workspaceRoot));
731
+ const target = await ctx.fs.resolve(input.filePath, sessionResolveOptions(exec, sandboxPolicy?.workspaceRoot));
799
732
  let outcome;
800
733
  try {
801
734
  const intent = await ctx.waterfall("fs/edit-intent", target, exec, () => void 0);
@@ -1040,7 +973,7 @@ function imageReadContent(value) {
1040
973
  function applyReadImageTool(ctx) {
1041
974
  ctx.tools.register(defineTool({
1042
975
  name: "read_image",
1043
- description: "Read a PNG/JPEG/WebP/GIF file and return the image itself. A path without a file extension is accepted; the format is detected from the file content, so normalized attachment paths can be passed directly without copying or renaming. Harness validates and downscales large supported images before the next model request, so use this tool directly instead of installing image libraries or creating thumbnails merely to inspect an image. Independent files may be read concurrently in small batches. Requires the current model to accept image input.",
976
+ description: "Read a PNG/JPEG/WebP/GIF file and return the image itself. Large images are downscaled automatically; do not install image libraries or create thumbnails to inspect an image.",
1044
977
  parameters: { file_path: {
1045
978
  type: "string",
1046
979
  required: true,
@@ -1165,18 +1098,19 @@ var FsSandboxController = class {
1165
1098
  sandbox_permissions: {
1166
1099
  type: "string",
1167
1100
  enum: [...this.escalationModes],
1168
- description: "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval."
1101
+ description: sandboxPermissionsDescription("operation")
1169
1102
  },
1170
1103
  justification: {
1171
1104
  type: "string",
1172
- description: "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access."
1105
+ description: "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access. Use the language of the user’s current request."
1173
1106
  }
1174
1107
  };
1175
1108
  }
1176
1109
  /**
1177
1110
  * The policy to stamp onto this mutation: an approved escalation grant (a
1178
1111
  * strictly wider retry resolved through `ctx.approval` before anything
1179
- * executes), else the session's standing mode. The calling session's cwd is
1112
+ * executes), else the session's standing mode. Repeating the standing mode
1113
+ * requires no approval. The calling session's cwd is
1180
1114
  * always carried as the workspace root. Validates the escalation argument
1181
1115
  * pairing first.
1182
1116
  * @param toolName - the mutating tool's name, for the approval audit trail.
@@ -8,13 +8,16 @@ import type { FileDiff } from '@x1a0f3n9/dsh-tools';
8
8
  export declare const DIFF_CONTEXT = 3;
9
9
  /**
10
10
  * The `write`/`edit` tools' private `tool/result` `meta` payload: the applied
11
- * contextual-diff hunks. Attached opaquely (as `unknown`) on the tool result and
11
+ * contextual-diff hunks, and for `write` whether the call created or updated
12
+ * the file, which tells an empty hunk list of a create from one of an
13
+ * unchanged overwrite. Attached opaquely (as `unknown`) on the tool result and
12
14
  * persisted with the session log — it must be JSON-serializable (the session
13
15
  * validates this at `append`), so `presentResult` reproduces the diff card on
14
16
  * replay. The producing tool owns and narrows this opaque shape.
15
17
  */
16
18
  export type FsDiffMeta = {
17
19
  diffs: FileDiff[];
20
+ operation?: 'create' | 'update';
18
21
  };
19
22
  /**
20
23
  * Compute one {@link FileDiff} per hunk between `before` and `after`, each carrying the
@@ -64,13 +64,16 @@ export declare function buildWindow(chunks: AsyncIterable<string> | Iterable<str
64
64
  */
65
65
  export declare function formatReadOutput(displayPath: string, outcome: FileReadOutcome): string;
66
66
  /**
67
- * Derive a syntax-highlighting language hint from a read path's file extension.
68
- * Pure and case-insensitive on the extension; a dotfile with no extension
69
- * (`.gitignore`) and an unknown extension both yield `undefined`.
67
+ * Derive the persisted `lang` hint from a read path's file extension. The shared
68
+ * table in `@x1a0f3n9/dsh-util-code-language` owns the recognized suffixes and
69
+ * the path rules (both separators, a leading dot as the extension separator, and
70
+ * prototype-key safety); `readLangHintForPath` projects the read card's short ids
71
+ * over it, so a suffix whose value a recorded session already holds keeps it
72
+ * byte-identical while every other suffix uses its language's short name.
70
73
  * @param path - the model-facing path the read reported.
71
- * @returns the language hint for {@link LANG_BY_EXTENSION}, or `undefined` when the extension maps to none.
74
+ * @returns the persisted language hint, or `undefined` when the extension maps to none.
72
75
  */
73
- export declare function langFromPath(path: string): string | undefined;
76
+ export { readLangHintForPath as langFromPath } from '@x1a0f3n9/dsh-util-code-language';
74
77
  /**
75
78
  * The `read` tool's private `tool/result` `meta` payload: the structured
76
79
  * line-numbered window a capable UI renders as a code view. Attached opaquely (as
@@ -52,7 +52,8 @@ export declare class FsSandboxController {
52
52
  /**
53
53
  * The policy to stamp onto this mutation: an approved escalation grant (a
54
54
  * strictly wider retry resolved through `ctx.approval` before anything
55
- * executes), else the session's standing mode. The calling session's cwd is
55
+ * executes), else the session's standing mode. Repeating the standing mode
56
+ * requires no approval. The calling session's cwd is
56
57
  * always carried as the workspace root. Validates the escalation argument
57
58
  * pairing first.
58
59
  * @param toolName - the mutating tool's name, for the approval audit trail.
@@ -10,19 +10,16 @@ import type { ToolExecution } from '@x1a0f3n9/dsh-tools';
10
10
  /**
11
11
  * The session workspace cwd for this call, or `undefined` when none applies.
12
12
  * @param exec - the tool-execution context; only its optional `agent` is read.
13
- * @param requestedPath - the path the provider will resolve; parent traversal
14
- * makes a symlinked cwd's filesystem identity observable.
15
13
  * @returns the calling agent's session cwd, or undefined for a non-agent caller (the backend then applies its own default).
16
14
  */
17
- export declare function sessionCwd(exec: ToolExecution, requestedPath: string): string | undefined;
15
+ export declare function sessionCwd(exec: ToolExecution): string | undefined;
18
16
  /**
19
17
  * Resolution options shared by all model-facing filesystem tools.
20
18
  * @param exec - the tool-execution context supplying session cwd and cancellation.
21
- * @param requestedPath - the path the provider will resolve.
22
19
  * @param policyWorkspaceRoot - resolved per-call root, when a mutation carries sandbox policy.
23
20
  * @returns provider resolution options for the current tool call.
24
21
  */
25
- export declare function sessionResolveOptions(exec: ToolExecution, requestedPath: string, policyWorkspaceRoot?: string): {
22
+ export declare function sessionResolveOptions(exec: ToolExecution, policyWorkspaceRoot?: string): {
26
23
  cwd?: string;
27
24
  signal?: AbortSignal;
28
25
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@x1a0f3n9/dsh-tool-fs",
3
3
  "description": "Model-facing filesystem tools (read, write, edit) over the DeepSeek Harness filesystem seam (ctx.fs)",
4
- "version": "0.1.5-rc.4",
4
+ "version": "0.1.7-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -28,39 +28,41 @@
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
30
  "diff": "^9.0.0",
31
- "@deepseek-ai/schemastery": "^3.18.2"
31
+ "@deepseek-ai/schemastery": "~3.18.4",
32
+ "@x1a0f3n9/dsh-util-code-language": "0.1.7-rc.2"
32
33
  },
33
34
  "peerDependencies": {
34
- "@x1a0f3n9/dsh-attachment": "^0.1.5-rc.4",
35
- "@x1a0f3n9/dsh-fs": "^0.1.5-rc.4",
36
- "@x1a0f3n9/dsh-llm": "^0.1.5-rc.4",
37
- "@x1a0f3n9/dsh-sandbox": "^0.1.5-rc.4",
38
- "@x1a0f3n9/dsh-sandbox-policy": "^0.1.5-rc.4",
39
- "@x1a0f3n9/dsh-session": "^0.1.5-rc.4",
40
- "@x1a0f3n9/dsh-system-prompt": "^0.1.5-rc.4",
41
- "@x1a0f3n9/dsh-tools": "^0.1.5-rc.4",
42
- "@x1a0f3n9/dsh-user-approval": "^0.1.5-rc.4",
43
- "@deepseek-ai/cordis": "^4.0.2"
35
+ "@x1a0f3n9/dsh-attachment": "0.1.7-rc.2",
36
+ "@x1a0f3n9/dsh-fs": "0.1.7-rc.2",
37
+ "@x1a0f3n9/dsh-llm": "0.1.7-rc.2",
38
+ "@x1a0f3n9/dsh-sandbox": "0.1.7-rc.2",
39
+ "@x1a0f3n9/dsh-sandbox-policy": "0.1.7-rc.2",
40
+ "@x1a0f3n9/dsh-session": "0.1.7-rc.2",
41
+ "@x1a0f3n9/dsh-system-prompt": "0.1.7-rc.2",
42
+ "@x1a0f3n9/dsh-tools": "0.1.7-rc.2",
43
+ "@x1a0f3n9/dsh-user-approval": "0.1.7-rc.2",
44
+ "@deepseek-ai/cordis": "~4.0.4"
44
45
  },
45
46
  "devDependencies": {
46
- "@x1a0f3n9/dsh-agent-loop": "^0.1.5-rc.4",
47
- "@x1a0f3n9/dsh-agent": "^0.1.5-rc.4",
48
- "@x1a0f3n9/dsh-agent-loop-testkit": "^0.1.5-rc.4",
49
- "@x1a0f3n9/dsh-attachment": "^0.1.5-rc.4",
50
- "@x1a0f3n9/dsh-fs": "^0.1.5-rc.4",
51
- "@x1a0f3n9/dsh-fs-local": "^0.1.5-rc.4",
52
- "@x1a0f3n9/dsh-fs-observation-policy": "^0.1.5-rc.4",
53
- "@x1a0f3n9/dsh-llm": "^0.1.5-rc.4",
54
- "@x1a0f3n9/dsh-llm-deepseek": "^0.1.5-rc.4",
55
- "@x1a0f3n9/dsh-sandbox": "^0.1.5-rc.4",
56
- "@x1a0f3n9/dsh-sandbox-policy": "^0.1.5-rc.4",
57
- "@x1a0f3n9/dsh-session": "^0.1.5-rc.4",
58
- "@x1a0f3n9/dsh-system-prompt": "^0.1.5-rc.4",
59
- "@x1a0f3n9/dsh-tools": "^0.1.5-rc.4",
60
- "@x1a0f3n9/dsh-user-approval": "^0.1.5-rc.4",
61
- "@deepseek-ai/cordis": "^4.0.2",
62
- "@x1a0f3n9/dsh-session-projection": "^0.1.5-rc.4",
63
- "@x1a0f3n9/dsh-scope": "^0.1.5-rc.4",
64
- "@x1a0f3n9/dsh-code-runtime": "^0.1.5-rc.4"
47
+ "@x1a0f3n9/dsh-agent": "0.1.7-rc.2",
48
+ "@x1a0f3n9/dsh-agent-loop": "0.1.7-rc.2",
49
+ "@x1a0f3n9/dsh-agent-loop-testkit": "0.1.7-rc.2",
50
+ "@x1a0f3n9/dsh-attachment": "0.1.7-rc.2",
51
+ "@x1a0f3n9/dsh-fs": "0.1.7-rc.2",
52
+ "@x1a0f3n9/dsh-fs-local": "0.1.7-rc.2",
53
+ "@x1a0f3n9/dsh-fs-observation-policy": "0.1.7-rc.2",
54
+ "@x1a0f3n9/dsh-llm": "0.1.7-rc.2",
55
+ "@x1a0f3n9/dsh-llm-deepseek": "0.1.7-rc.2",
56
+ "@x1a0f3n9/dsh-llm-deepseek-api-key": "0.1.7-rc.2",
57
+ "@x1a0f3n9/dsh-sandbox": "0.1.7-rc.2",
58
+ "@x1a0f3n9/dsh-sandbox-policy": "0.1.7-rc.2",
59
+ "@x1a0f3n9/dsh-session": "0.1.7-rc.2",
60
+ "@x1a0f3n9/dsh-system-prompt": "0.1.7-rc.2",
61
+ "@x1a0f3n9/dsh-tools": "0.1.7-rc.2",
62
+ "@x1a0f3n9/dsh-user-approval": "0.1.7-rc.2",
63
+ "@deepseek-ai/cordis": "~4.0.4",
64
+ "@x1a0f3n9/dsh-session-projection": "0.1.7-rc.2",
65
+ "@x1a0f3n9/dsh-scope": "0.1.7-rc.2",
66
+ "@x1a0f3n9/dsh-ptc-runtime": "0.1.7-rc.2"
65
67
  }
66
68
  }