@deepseek-ai/dsh-fs-local 0.1.6-alpha.2 → 0.1.7-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 +2 -2
- package/README.md +6 -3
- package/README.zh.md +6 -3
- package/lib/index.js +27 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +8 -7
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:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 9c005443eb064a03b3050c9ae82295c794315239
|
|
6
|
+
README.zh.md: 3adc249afe68b4cd8b56c68101baafec47ffb981
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
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.
|
|
12
|
+
Use `dsh-fs-local` to read, list, watch, 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.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -52,7 +52,9 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
|
|
52
52
|
|
|
53
53
|
Read any regular UTF-8 text file whole or as a stream, read raw bytes up to a cap you choose or in a 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
|
+
Read, listing, and mutation 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 an edit reports `FS_STALE_VERSION` whether or not the version guard is supplied.
|
|
56
|
+
|
|
57
|
+
Chokidar observes one file or a directory's direct entries through OS events, without polling or recursive watching. Files use a filtered parent-directory watch, so readiness also covers creation of an initially missing file. File watches cover in-place writes, atomic replacement, deletion, and same-path recreation while the parent directory remains.
|
|
56
58
|
|
|
57
59
|
-----
|
|
58
60
|
|
|
@@ -90,7 +92,7 @@ Each edit probes, verifies the version guard before literal matching (so stale e
|
|
|
90
92
|
|
|
91
93
|
### Ownership and invariants
|
|
92
94
|
|
|
93
|
-
Raw I/O is Cordis-free and independently unit-tested in `src/fsio.ts`; `src/index.ts` stays thin wiring. `config.cwd` is a resolution default only — containment is the job of `fs-sandbox` or a `tools/execute` permission plugin. Cancellation is a best-effort `AbortSignal` checked before and after each asynchronous probe.
|
|
95
|
+
Raw I/O is Cordis-free and independently unit-tested in `src/fsio.ts`; `src/index.ts` stays thin wiring. `config.cwd` is a resolution default only — containment is the job of `fs-sandbox` or a `tools/execute` permission plugin. Cancellation is a best-effort `AbortSignal` checked before and after each asynchronous probe. For watches, the signal cancels initialization; after readiness, the caller must await the returned close function.
|
|
94
96
|
|
|
95
97
|
</details>
|
|
96
98
|
|
|
@@ -127,6 +129,7 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
|
|
127
129
|
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
130
|
|
|
129
131
|
- **`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.
|
|
132
|
+
- **Linux parent-directory recreation** — restoring observation after a parent directory is deleted and recreated is deferred; same-path file recreation while its parent remains is supported.
|
|
130
133
|
- **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
134
|
- **`editText` holds the whole file (plus the edited copy) in memory** — streaming exists only on the read path.
|
|
132
135
|
- **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`
|
|
12
|
+
使用 `dsh-fs-local` 可在宿主文件系统上读取、列出、监听、原子写入和编辑文件。相对路径从可配置的基准目录解析,而绝对路径和父目录遍历不受限制。到达同一文件的路径和符号链接共享一个身份。写入保留文件权限,可选版本防护会拒绝陈旧覆盖。直接访问宿主文件时选择本包;需要约束变更时使用 `fs-sandbox`。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -52,7 +52,9 @@ kind: "package-reference"
|
|
|
52
52
|
|
|
53
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
|
+
|
|
57
|
+
Chokidar 通过 OS 事件观察单个文件或目录的直接子项,不使用轮询或递归监听。文件使用筛选到目标的父目录监听,因此就绪时也能接收起初缺失文件的创建事件。父目录保持存在时,文件监听覆盖原地写入、原子替换、删除和同路径重建。
|
|
56
58
|
|
|
57
59
|
-----
|
|
58
60
|
|
|
@@ -90,7 +92,7 @@ kind: "package-reference"
|
|
|
90
92
|
|
|
91
93
|
### 归属与不变式
|
|
92
94
|
|
|
93
|
-
原始 I/O 不依赖 Cordis,在 `src/fsio.ts` 中独立单元测试;`src/index.ts` 保持为轻量接线。`config.cwd` 只是解析默认值——约束是 `fs-sandbox` 或 `tools/execute` 权限插件的工作。取消是尽力而为的 `AbortSignal
|
|
95
|
+
原始 I/O 不依赖 Cordis,在 `src/fsio.ts` 中独立单元测试;`src/index.ts` 保持为轻量接线。`config.cwd` 只是解析默认值——约束是 `fs-sandbox` 或 `tools/execute` 权限插件的工作。取消是尽力而为的 `AbortSignal`,在每次异步探测前后检查。对于监听,signal 取消初始化;就绪后,调用方必须等待返回的关闭函数完成。
|
|
94
96
|
|
|
95
97
|
</details>
|
|
96
98
|
|
|
@@ -127,6 +129,7 @@ kind: "package-reference"
|
|
|
127
129
|
这些限制说明本地后端何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用文件系统对比或任务积压。
|
|
128
130
|
|
|
129
131
|
- **`config.cwd` 不是沙箱**:它是解析默认值,而非约束;绝对路径和 `..` 可以逃逸。请使用更严格的 `ctx.fs` 后端或 `tools/execute` waterfall(瀑布式事件)上的权限插件实施约束。
|
|
132
|
+
- **Linux 父目录重建**:父目录删除并重建后的监听恢复仍延期;父目录保持存在时,同路径文件重建仍受支持。
|
|
130
133
|
- **版本 token 依赖文件系统元数据**:它们组合设备、inode、大小、纳秒级 mtime 与纳秒级 ctime;如果存储层在重写时无法更新其中任何一项事实,仍可能绕过陈旧防护。
|
|
131
134
|
- **`editText` 会把整个文件及编辑后的副本保存在内存中**:只有读取路径支持流式处理。
|
|
132
135
|
- **低于上限的覆写仍会缓冲上下文基础**:`writeText` 除调用方持有的替换内容外,最多还会保留略低于 `config.diffBasisMaxBytes` 的旧文本;该上限不限制返回的 `after` 值,也不限制整文件展示回退。
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { constants } from "node:buffer";
|
|
2
|
+
import { once } from "node:events";
|
|
3
|
+
import { watch } from "chokidar";
|
|
2
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep, toNamespacedPath } from "node:path";
|
|
3
5
|
import { pathToFileURL } from "node:url";
|
|
4
6
|
import z from "@deepseek-ai/schemastery";
|
|
@@ -721,6 +723,31 @@ const MAX_DIFF_BASIS_BYTES = Math.min(constants.MAX_LENGTH, constants.MAX_STRING
|
|
|
721
723
|
* containment with a stricter backend or a `tools/execute` permission plugin.
|
|
722
724
|
*/
|
|
723
725
|
var LocalFileSystem = class extends FileSystem {
|
|
726
|
+
async watch(target, changed, signal) {
|
|
727
|
+
signal.throwIfAborted();
|
|
728
|
+
const path = resolve(this.processPath(target));
|
|
729
|
+
const directory = (await this.stat(target, signal))?.type === "directory";
|
|
730
|
+
signal.throwIfAborted();
|
|
731
|
+
const root = directory ? path : dirname(path);
|
|
732
|
+
const watcher = watch(root, {
|
|
733
|
+
ignoreInitial: true,
|
|
734
|
+
depth: 0,
|
|
735
|
+
ignored: (entry) => !directory && resolve(entry) !== root && resolve(entry) !== path
|
|
736
|
+
});
|
|
737
|
+
watcher.on("all", (_event, entry) => {
|
|
738
|
+
if (directory || resolve(entry) === path) changed();
|
|
739
|
+
});
|
|
740
|
+
watcher.on("error", (error) => {
|
|
741
|
+
changed(error instanceof Error ? error : new Error(String(error)));
|
|
742
|
+
});
|
|
743
|
+
try {
|
|
744
|
+
await once(watcher, "ready", { signal });
|
|
745
|
+
return () => watcher.close();
|
|
746
|
+
} catch (error) {
|
|
747
|
+
await watcher.close();
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
724
751
|
static Config = z.object({
|
|
725
752
|
cwd: z.string().default(process.cwd()),
|
|
726
753
|
diffBasisMaxBytes: z.number().default(DEFAULT_DIFF_BASIS_MAX_BYTES)
|
package/lib/types/index.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ type ResolvedConfig = Required<Config>;
|
|
|
26
26
|
* containment with a stricter backend or a `tools/execute` permission plugin.
|
|
27
27
|
*/
|
|
28
28
|
export declare class LocalFileSystem extends FileSystem {
|
|
29
|
+
watch(target: FsTarget, changed: (error?: Error) => void, signal: AbortSignal): Promise<() => Promise<void>>;
|
|
29
30
|
static Config: z<Config>;
|
|
30
31
|
/** Validated config (schemastery applied the defaults before construction). */
|
|
31
32
|
readonly config: ResolvedConfig;
|
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.
|
|
4
|
+
"version": "0.1.7-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,16 +27,17 @@
|
|
|
27
27
|
],
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
31
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
30
|
+
"@deepseek-ai/dsh-fs": "^0.1.7-alpha.1",
|
|
31
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
+
"chokidar": "^4.0.3",
|
|
34
35
|
"koffi": "^3.1.0",
|
|
35
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.3"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@deepseek-ai/dsh-fs": "^0.1.
|
|
39
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
40
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
39
|
+
"@deepseek-ai/dsh-fs": "^0.1.7-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-llm": "^0.1.7-alpha.1",
|
|
41
|
+
"@deepseek-ai/cordis": "^4.0.3"
|
|
41
42
|
}
|
|
42
43
|
}
|