@deepseek-ai/dsh-fs-local 0.1.2-rc.1 → 0.1.5-alpha.1

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
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/fs/fs-local/README.md
5
- README.md: 3ac8dcab56479add82b3d8d8a774c22022d5687b
6
- README.zh.md: b65055d1bd7af5ce629cb376e8f83205b96dccde
5
+ README.md: 427cf02631f9d56246851f7fc67c92dd662c2a62
6
+ README.zh.md: 7009d9dd853b5443780978036792f10ebc9706b5
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `dsh-fs-local` implements the `ctx.fs` filesystem contract ([`dsh-fs`](../fs/README.md)) on the host filesystem: loading it as a plugin populates `ctx.fs` with real file access — resolve, read, list, atomic write, and literal edit against the local machine's files. Relative paths resolve from a configurable base directory, and the same file reached through different paths or symlinks shares one identity. Because this backend shares the host filesystem, it can also map an absolute host path into the process path used by this execution world. Writes are atomic and preserve file permissions; the optional version guard makes stale overwrites fail instead of clobbering. Choose it when a process needs direct, unconfined access to host files; choose `fs-sandbox` when mutations must be confined, or `fs-e2b` when file state belongs in a remote execution world.
12
+ Use `dsh-fs-local` to read, list, atomically write, and edit files on the host filesystem. Relative paths resolve from a configurable base directory, while absolute paths and parent traversal remain unrestricted. Paths and symlinks that reach the same file share one identity. Writes preserve file permissions, and optional version guards reject stale overwrites. Choose this package for direct host access; use `fs-sandbox` for confined mutations or `fs-e2b` for files in a remote execution world.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -50,7 +50,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
50
50
 
51
51
  ### What you can do
52
52
 
53
- Read any regular UTF-8 text file whole or as a stream, read raw bytes up to a cap you choose, and list one directory level in stable name order. Create or replace a file atomically, and apply a literal text edit atomically; both mutations serialize per file, so concurrent writers never interleave. The version guard is optional: omit it for unconditional create-or-overwrite, or supply it to fail when the file changed since you last observed it.
53
+ Read any regular UTF-8 text file whole or as a stream, read raw bytes up to a cap you choose or as one byte window, and list one directory level in stable name order. Create or replace a file atomically, and apply a literal text edit atomically; both mutations serialize per file, so concurrent writers never interleave. The version guard is optional: omit it for unconditional create-or-overwrite, or supply it to fail when the file changed since you last observed it.
54
54
 
55
55
  Failures are typed `FsError`s with stable codes — `FS_NOT_FOUND`, `FS_NOT_TEXT` (binary content), `FS_STALE_VERSION` (changed since observation), `FS_EDIT_NOT_FOUND` or `FS_AMBIGUOUS_EDIT` (no unique literal match), and others — so callers branch on the code, never on message text. A missing target on a guarded edit reports `FS_STALE_VERSION` either way.
56
56
 
@@ -106,7 +106,7 @@ Read these pages when the package-level contract is not enough. They move from t
106
106
  - [fs-sandbox](../fs-sandbox/README.md) — the sandbox-enforcing backend that extends this one.
107
107
  - [tool-fs](../tool-fs/README.md) — the model-facing tools that consume `ctx.fs`.
108
108
  - [fs-observation-policy](../fs-observation-policy/README.md) — the policy plugin that guards mutations through the `fs/*` events.
109
- - [Windows DACL preservation note](../../../.agents/notes/implemented/bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md) — why atomic replacement copies the target's access policy.
109
+ - [Windows DACL preservation note](../../../.agents/notes/archived/bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md) — why atomic replacement copies the target's access policy.
110
110
 
111
111
  -----
112
112
 
@@ -126,7 +126,7 @@ No direct invalidation; the named consumer owns any request-prefix changes.
126
126
 
127
127
  These limits define when the local backend is a poor fit or needs special operational care. They are current package constraints, not a general filesystem comparison or a task backlog.
128
128
 
129
- - **`config.cwd` is not a sandbox** — it is a resolution default, not containment: absolute paths and `..` escape it. Enforce containment with a stricter `ctx.fs` backend or a permission plugin on the `tools/execute` waterfall ([capability-seam note](../../../.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md)).
129
+ - **`config.cwd` is not a sandbox** — it is a resolution default, not containment: absolute paths and `..` escape it. Enforce containment with a stricter `ctx.fs` backend or a permission plugin on the `tools/execute` waterfall.
130
130
  - **Version tokens depend on filesystem metadata** — they combine device, inode, size, nanosecond mtime, and nanosecond ctime; a storage layer that cannot update any of those facts for a rewrite can still defeat the stale guard.
131
131
  - **`editText` holds the whole file (plus the edited copy) in memory** — streaming exists only on the read path.
132
132
  - **A sub-limit overwrite still buffers a contextual basis** — `writeText` may retain up to just below `config.diffBasisMaxBytes` of prior text in addition to the caller-owned replacement; the bound does not cap the returned `after` value or the whole-file presentation fallback.
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `dsh-fs-local` 在宿主文件系统上实现 `ctx.fs` 文件系统约定([`dsh-fs`](../fs/README.zh.md)):把它作为插件加载后,`ctx.fs` 就拥有真实的文件访问能力——针对本机文件的解析、读取、列出、原子写入与字面量编辑。相对路径从可配置的基准目录解析,经不同路径或符号链接到达的同一文件共享一个身份。由于本后端共享宿主文件系统,它还可以把绝对宿主路径映射为此执行世界使用的进程路径。写入是原子的并保留文件权限;可选版本防护让陈旧覆盖失败而不是静默覆盖。当进程需要直接、不受约束地访问宿主文件时选择它;需要约束变更时选择 `fs-sandbox`,文件状态属于远程执行世界时选择 `fs-e2b`。
12
+ 使用 `dsh-fs-local` 可在宿主文件系统上读取、列出、原子写入和编辑文件。相对路径从可配置的基准目录解析,而绝对路径和父目录遍历不受限制。到达同一文件的路径和符号链接共享一个身份。写入保留文件权限,可选版本防护会拒绝陈旧覆盖。直接访问宿主文件时选择本包;需要约束变更时使用 `fs-sandbox`,文件位于远程执行世界时使用 `fs-e2b`。
13
13
 
14
14
  ## 目录
15
15
 
@@ -50,7 +50,7 @@ kind: "package-reference"
50
50
 
51
51
  ### 你能做什么
52
52
 
53
- 完整或流式读取任意普通 UTF-8 文本文件,按你选择的上限读取原始字节,并按稳定名称顺序列出一层目录。原子地创建或替换文件,并原子地应用字面量文本编辑;两个变更操作都按文件串行化,并发写入方绝不会交错。版本防护是可选的:省略它即无条件创建或覆盖,提供它则在文件自上次观察以来发生变化时失败。
53
+ 完整或流式读取任意普通 UTF-8 文本文件,按你选择的上限或按字节窗口读取原始字节,并按稳定名称顺序列出一层目录。原子地创建或替换文件,并原子地应用字面量文本编辑;两个变更操作都按文件串行化,并发写入方绝不会交错。版本防护是可选的:省略它即无条件创建或覆盖,提供它则在文件自上次观察以来发生变化时失败。
54
54
 
55
55
  失败是携带稳定错误码的类型化 `FsError`——`FS_NOT_FOUND`、`FS_NOT_TEXT`(二进制内容)、`FS_STALE_VERSION`(自观察以来已变化)、`FS_EDIT_NOT_FOUND` 或 `FS_AMBIGUOUS_EDIT`(无唯一字面量匹配)等——因此调用方依据错误码分支,绝不解析消息文本。带防护的编辑遇到缺失目标时,无论哪种情况都报告 `FS_STALE_VERSION`。
56
56
 
@@ -106,7 +106,7 @@ kind: "package-reference"
106
106
  - [fs-sandbox](../fs-sandbox/README.zh.md)——扩展本后端的沙箱强制后端。
107
107
  - [tool-fs](../tool-fs/README.zh.md)——消费 `ctx.fs` 的面向模型工具。
108
108
  - [fs-observation-policy](../fs-observation-policy/README.zh.md)——通过 `fs/*` 事件防护变更的策略插件。
109
- - [Windows DACL 保留笔记](../../../.agents/notes/implemented/bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.zh.md)——原子替换为何复制目标的访问策略。
109
+ - [Windows DACL 保留笔记](../../../.agents/notes/archived/bug-fix/2026-07-19-windows-atomic-write-dacl-preservation.md)——原子替换为何复制目标的访问策略。
110
110
 
111
111
  -----
112
112
 
@@ -126,7 +126,7 @@ kind: "package-reference"
126
126
 
127
127
  这些限制说明本地后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用文件系统对比或任务积压。
128
128
 
129
- - **`config.cwd` 不是沙箱**:它是解析默认值,而非约束;绝对路径和 `..` 可以逃逸。请使用更严格的 `ctx.fs` 后端或 `tools/execute` waterfall(瀑布式事件)上的权限插件实施约束(见[能力 seam 笔记](../../../.agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.zh.md))。
129
+ - **`config.cwd` 不是沙箱**:它是解析默认值,而非约束;绝对路径和 `..` 可以逃逸。请使用更严格的 `ctx.fs` 后端或 `tools/execute` waterfall(瀑布式事件)上的权限插件实施约束。
130
130
  - **版本 token 依赖文件系统元数据**:它们组合设备、inode、大小、纳秒级 mtime 与纳秒级 ctime;如果存储层在重写时无法更新其中任何一项事实,仍可能绕过陈旧防护。
131
131
  - **`editText` 会把整个文件及编辑后的副本保存在内存中**:只有读取路径支持流式处理。
132
132
  - **低于上限的覆写仍会缓冲上下文基础**:`writeText` 除调用方持有的替换内容外,最多还会保留略低于 `config.diffBasisMaxBytes` 的旧文本;该上限不限制返回的 `after` 值,也不限制整文件展示回退。
package/lib/index.js CHANGED
@@ -390,6 +390,39 @@ async function readWholeBytes(target, signal, maxBytes, internals = {}) {
390
390
  return Buffer.concat(chunks, bytes);
391
391
  }
392
392
  /**
393
+ * Read the bytes at `[offset, offset + length)` of a regular file with no
394
+ * decoding or binary rejection. The window is the bound: the stream opens at
395
+ * `offset` and closes after `length` bytes, so no more than the window is ever
396
+ * buffered whatever the file's size; a window at or past the end is empty.
397
+ * @param target - the resolved file to read.
398
+ * @param range - `offset`, the 0-based first byte, and `length`, the largest byte count.
399
+ * @param signal - aborts the read (`FS_ABORTED`).
400
+ * @returns the window's bytes, at most `length` long.
401
+ */
402
+ async function readByteWindow(target, range, signal) {
403
+ await statRegularFile(target, "read", signal);
404
+ if (range.length === 0) return new Uint8Array(0);
405
+ const stream = createReadStream(target.targetKey, {
406
+ start: range.offset,
407
+ end: range.offset + range.length - 1,
408
+ ...signal ? { signal } : {}
409
+ });
410
+ const chunks = [];
411
+ let bytes = 0;
412
+ try {
413
+ for await (const chunk of stream) {
414
+ chunks.push(chunk);
415
+ bytes += chunk.length;
416
+ }
417
+ } catch (error) {
418
+ /* v8 ignore next 2 -- a mid-stream abort needs cancellation racing an active read; pre-abort is deterministic. */
419
+ if (isAbortError(error)) throw new FsError("read aborted", "FS_ABORTED");
420
+ /* v8 ignore next -- any other stream failure needs an I/O fault after a successful stat. */
421
+ throw error;
422
+ }
423
+ return Buffer.concat(chunks, bytes);
424
+ }
425
+ /**
393
426
  * Stream a whole regular UTF-8 text file as decoded text chunks. Same text
394
427
  * semantics as {@link readWholeText} (regular-file check, binary/NUL rejection,
395
428
  * cross-chunk UTF-8 decoding), but never holds the whole file in memory.
@@ -764,6 +797,12 @@ var LocalFileSystem = class extends FileSystem {
764
797
  targetKey: target.targetKey
765
798
  }, signal, maxBytes, this.internals);
766
799
  }
800
+ async readByteRange(target, range, signal) {
801
+ return readByteWindow({
802
+ displayPath: target.displayPath,
803
+ targetKey: target.targetKey
804
+ }, range, signal);
805
+ }
767
806
  async listDir(target, signal) {
768
807
  return (await listDirectory({
769
808
  displayPath: target.displayPath,
@@ -114,6 +114,20 @@ export declare function readWholeText(target: LocalTarget, signal?: AbortSignal)
114
114
  * @returns the full raw content, at most `maxBytes` long.
115
115
  */
116
116
  export declare function readWholeBytes(target: LocalTarget, signal: AbortSignal | undefined, maxBytes: number, internals?: FsIoInternals): Promise<Uint8Array>;
117
+ /**
118
+ * Read the bytes at `[offset, offset + length)` of a regular file with no
119
+ * decoding or binary rejection. The window is the bound: the stream opens at
120
+ * `offset` and closes after `length` bytes, so no more than the window is ever
121
+ * buffered whatever the file's size; a window at or past the end is empty.
122
+ * @param target - the resolved file to read.
123
+ * @param range - `offset`, the 0-based first byte, and `length`, the largest byte count.
124
+ * @param signal - aborts the read (`FS_ABORTED`).
125
+ * @returns the window's bytes, at most `length` long.
126
+ */
127
+ export declare function readByteWindow(target: LocalTarget, range: {
128
+ offset: number;
129
+ length: number;
130
+ }, signal?: AbortSignal): Promise<Uint8Array>;
117
131
  /**
118
132
  * Stream a whole regular UTF-8 text file as decoded text chunks. Same text
119
133
  * semantics as {@link readWholeText} (regular-file check, binary/NUL rejection,
@@ -53,6 +53,10 @@ export declare class LocalFileSystem extends FileSystem {
53
53
  readText(target: FsTarget, signal?: AbortSignal): Promise<string>;
54
54
  streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>;
55
55
  readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
56
+ readByteRange(target: FsTarget, range: {
57
+ offset: number;
58
+ length: number;
59
+ }, signal?: AbortSignal): Promise<Uint8Array>;
56
60
  listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>;
57
61
  writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise<FsWriteOutcome>;
58
62
  editText(target: FsTarget, edit: FsEditRequest, expected?: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-fs-local",
3
3
  "description": "Local-filesystem implementation of the DeepSeek Harness filesystem seam (ctx.fs)",
4
- "version": "0.1.2-rc.1",
4
+ "version": "0.1.5-alpha.1",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,7 +27,7 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-fs": "^0.1.2-rc.1",
30
+ "@deepseek-ai/dsh-fs": "^0.1.5-alpha.1",
31
31
  "@deepseek-ai/cordis": "^4.0.2"
32
32
  },
33
33
  "dependencies": {
@@ -35,8 +35,8 @@
35
35
  "@deepseek-ai/schemastery": "^3.18.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@deepseek-ai/dsh-fs": "^0.1.2-rc.1",
39
- "@deepseek-ai/dsh-llm": "^0.1.2-rc.1",
38
+ "@deepseek-ai/dsh-fs": "^0.1.5-alpha.1",
39
+ "@deepseek-ai/dsh-llm": "^0.1.5-alpha.1",
40
40
  "@deepseek-ai/cordis": "^4.0.2"
41
41
  }
42
42
  }