@deepseek-ai/dsh-spill-local 0.0.1-rc.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/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +34 -0
- package/README.zh.md +34 -0
- package/lib/index.js +138 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +39 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/store.d.ts +69 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/spill/spill-local/README.md
|
|
5
|
+
README.md: 2270a65d9270e1549a9e48d6a36b821e48c29070
|
|
6
|
+
README.zh.md: 907a2b5c98f3ee07e0b896b8827e6a00fa1827df
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-spill-local
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
The **local-filesystem** implementation of the [`@deepseek-ai/dsh-spill`](../spill) storage seam. Registers as `ctx.spillStore` and persists a tool's oversized text to a private, session-scoped file; its locator is the file path and its retrieval hint tells the model to use `read` or `grep` on that path.
|
|
6
|
+
|
|
7
|
+
## Storage layout
|
|
8
|
+
|
|
9
|
+
Files land at `<root>/session-<hash>/<random>-<safeName>`:
|
|
10
|
+
|
|
11
|
+
- **`root`** — the config `root` (resolved to absolute), or a lazily-created private (0700) per-process directory under the OS temp dir when omitted. A predictable, world-readable root would let other local users read spilled tool output or plant symlinks.
|
|
12
|
+
- **`session-<hash>`** — a short `sha256(sessionId)` prefix, so a session's spill files group together and a future cleanup can drop them per session.
|
|
13
|
+
- **`<random>-<safeName>`** — an unpredictable hex prefix (defeats symlink planting in a shared root) plus the caller's `suggestedName` sanitized to one safe path segment (traversal-proof; mirrors the JSONL persistence backend's `encodeSegment`). The write is exclusive + owner-only (`open(path, 'wx', 0o600)`): it fails on any pre-existing path, symlink or not, so a planted target cannot redirect it.
|
|
14
|
+
|
|
15
|
+
## Config
|
|
16
|
+
|
|
17
|
+
| Key | Default | Meaning |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `root` | private 0700 temp dir | Root directory for spill files. Set to keep them under a known location. |
|
|
20
|
+
|
|
21
|
+
`saveText` rejects on a real storage failure (permissions, ENOSPC); the spill policy treats a rejection as best-effort and keeps the inline result. See the seam README for the vocabulary and the [tool output spill Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md) for the design.
|
|
22
|
+
|
|
23
|
+
## Model Experience
|
|
24
|
+
|
|
25
|
+
Indirectly, through spill consumers that render the local path and `read`/`grep` retrieval guidance.
|
|
26
|
+
|
|
27
|
+
#### KV Cache effect
|
|
28
|
+
|
|
29
|
+
No direct invalidation; the named consumer owns any request-prefix changes.
|
|
30
|
+
|
|
31
|
+
## Known Limitations and Deferred Work
|
|
32
|
+
|
|
33
|
+
- **Local spill files persist until external cleanup** — the backend has no session-lifecycle deletion or age-based retention policy, because persisted, resumed, and forked sessions may still reference a path.
|
|
34
|
+
- **Locators require a co-located filesystem consumer** — a remote or virtual deployment needs another `SpillStore` backend whose locator and retrieval hint are meaningful there.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-spill-local
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
[`@deepseek-ai/dsh-spill`](../spill) 存储 seam 的**本地文件系统**实现。它注册为 `ctx.spillStore`,将工具产生的过大文本持久化到私有的会话级文件;定位信息是文件路径,取回指引会告诉模型对该路径使用 `read` 或 `grep`。
|
|
6
|
+
|
|
7
|
+
## 存储布局
|
|
8
|
+
|
|
9
|
+
文件存放在 `<root>/session-<hash>/<random>-<safeName>`:
|
|
10
|
+
|
|
11
|
+
- **`root`**:使用配置中的 `root`(解析为绝对路径);如果省略,则在操作系统临时目录下延迟创建每进程私有(0700)目录。可预测且任何用户均可读取的根目录会让其他本地用户读取 spill 工具输出,或在其中预置符号链接。
|
|
12
|
+
- **`session-<hash>`**:截短的 `sha256(sessionId)` 前缀,用于将同一会话的 spill 文件归在一起,以便未来的清理操作可按会话删除。
|
|
13
|
+
- **`<random>-<safeName>`**:不可预测的十六进制前缀(防止在共享根目录中预置符号链接),加上经过清理的调用方 `suggestedName`,使其成为单个安全路径段(防路径遍历;与 JSONL 持久化后端的 `encodeSegment` 一致)。写入操作采用排他方式,且权限仅限所有者(`open(path, 'wx', 0o600)`):如果路径已经存在,无论是否为符号链接,操作都会失败,因此预置的目标无法重定向写入。
|
|
14
|
+
|
|
15
|
+
## 配置
|
|
16
|
+
|
|
17
|
+
| 键 | 默认值 | 含义 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `root` | 私有 0700 临时目录 | spill 文件的根目录。设置后可将这些文件保存在已知位置。 |
|
|
20
|
+
|
|
21
|
+
`saveText` 在发生真实存储故障(权限、ENOSPC)时返回拒绝;spill 策略会按尽力而为原则处理该拒绝,并保留内联结果。词汇见 seam README,设计见[工具输出 spill Agent Note](../../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md)。
|
|
22
|
+
|
|
23
|
+
## 模型体验
|
|
24
|
+
|
|
25
|
+
通过渲染本地路径以及 `read`/`grep` 取回指引的 spill 消费方间接影响模型。
|
|
26
|
+
|
|
27
|
+
#### KV Cache 影响
|
|
28
|
+
|
|
29
|
+
不会直接导致 KV Cache 失效;请求前缀变更由上述消费方负责。
|
|
30
|
+
|
|
31
|
+
## 已知限制与暂缓事项
|
|
32
|
+
|
|
33
|
+
- **本地 spill 文件会持续存在,直到外部清理为止**:该后端不提供会话生命周期删除或按时间保留的策略,因为已持久化、已恢复和 fork 后的会话可能仍在引用某个路径。
|
|
34
|
+
- **定位信息需要与其位于同一文件系统的消费方**:远程或虚拟部署需要另一个 `SpillStore` 后端,其定位信息和取回指引在该环境中有明确含义。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { SpillLocator, SpillStore } from "@deepseek-ai/dsh-spill";
|
|
4
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
5
|
+
import { mkdtempSync } from "node:fs";
|
|
6
|
+
import { mkdir, open } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
//#region lib/types/store.js
|
|
9
|
+
/**
|
|
10
|
+
* Cordis-free storage mechanics for the local spill backend: private
|
|
11
|
+
* session-scoped directory selection, safe-name derivation, path-traversal
|
|
12
|
+
* protection, and the exclusive owner-only write. Kept out of the service class
|
|
13
|
+
* (like `dsh-bash-local`'s `run.ts`) so the filesystem behavior is unit-testable
|
|
14
|
+
* without a `ctx` and without the OS temp dir.
|
|
15
|
+
*
|
|
16
|
+
* @module @deepseek-ai/dsh-spill-local/store
|
|
17
|
+
*/
|
|
18
|
+
let defaultRoot;
|
|
19
|
+
/**
|
|
20
|
+
* The default spill root: a private (0700) per-process directory under the OS
|
|
21
|
+
* tmpdir, created lazily. Predictable world-readable paths would let other
|
|
22
|
+
* local users read spilled tool output or pre-create symlinks; `mkdtemp` gives
|
|
23
|
+
* an unpredictable suffix and 0700 semantics.
|
|
24
|
+
*
|
|
25
|
+
* @returns The lazily-created private spill root.
|
|
26
|
+
*/
|
|
27
|
+
function privateRoot() {
|
|
28
|
+
defaultRoot ??= mkdtempSync(join(tmpdir(), "dsh-spill-"));
|
|
29
|
+
return defaultRoot;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Encode an arbitrary string as one safe path segment, injectively over ALL JS
|
|
33
|
+
* (UTF-16) strings. A session id / suggested name is untrusted input, so this
|
|
34
|
+
* neutralizes `../`, absolute paths, NUL, and separators before any filesystem
|
|
35
|
+
* use. Each code unit is kept literal (`[A-Za-z0-9._-]`, minus `~`) or escaped
|
|
36
|
+
* as `~XXXX`; `~` is itself escaped, so the mapping is reversible and distinct
|
|
37
|
+
* inputs never collide. The whole-segment tokens `.`/`..` are escaped so they
|
|
38
|
+
* can never traverse. An empty string encodes to `~` (never an empty segment).
|
|
39
|
+
* (Mirrors the JSONL persistence backend's `encodeSegment`.)
|
|
40
|
+
*
|
|
41
|
+
* @param raw The untrusted string to encode as one safe path segment.
|
|
42
|
+
* @returns An injective, filesystem-safe single path segment.
|
|
43
|
+
*/
|
|
44
|
+
function encodeSegment(raw) {
|
|
45
|
+
if (raw.length === 0) return "~";
|
|
46
|
+
if (raw === ".") return "~002E";
|
|
47
|
+
if (raw === "..") return "~002E~002E";
|
|
48
|
+
let out = "";
|
|
49
|
+
for (let i = 0; i < raw.length; i++) {
|
|
50
|
+
const code = raw.charCodeAt(i);
|
|
51
|
+
const ch = String.fromCharCode(code);
|
|
52
|
+
if (ch !== "~" && /^[A-Za-z0-9._-]$/.test(ch)) out += ch;
|
|
53
|
+
else out += "~" + code.toString(16).toUpperCase().padStart(4, "0");
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The session-scoped directory: `<root>/session-<hash(sessionId)>`, a short stable hash.
|
|
59
|
+
*
|
|
60
|
+
* @param root The spill root directory.
|
|
61
|
+
* @param sessionId The owning session id to hash into a stable directory name.
|
|
62
|
+
* @returns The absolute session-scoped spill directory path.
|
|
63
|
+
*/
|
|
64
|
+
function sessionDir(root, sessionId) {
|
|
65
|
+
return join(root, `session-${createHash("sha256").update(sessionId).digest("hex").slice(0, 12)}`);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Write `content` to a fresh file under the session-scoped directory and return
|
|
69
|
+
* its path + byte length. The filename is a random hex prefix plus the
|
|
70
|
+
* sanitized `suggestedName`, so it is unpredictable (defeats symlink planting in
|
|
71
|
+
* a shared root) AND stays readable. The open is exclusive + owner-only
|
|
72
|
+
* (`'wx', 0o600`): it fails on any existing path — symlink or not — so a
|
|
73
|
+
* pre-planted target cannot redirect the write.
|
|
74
|
+
*
|
|
75
|
+
* @param options The resolved root and request fields required to save the file.
|
|
76
|
+
* @returns The written file path and UTF-8 byte length.
|
|
77
|
+
*/
|
|
78
|
+
async function saveTextFile(options) {
|
|
79
|
+
const dir = sessionDir(options.root, options.sessionId);
|
|
80
|
+
await mkdir(dir, {
|
|
81
|
+
recursive: true,
|
|
82
|
+
mode: 448
|
|
83
|
+
});
|
|
84
|
+
const safeName = encodeSegment(options.suggestedName);
|
|
85
|
+
const path = join(dir, `${randomBytes(6).toString("hex")}-${safeName}`);
|
|
86
|
+
const bytes = Buffer.byteLength(options.content, "utf8");
|
|
87
|
+
const handle = await open(path, "wx", 384);
|
|
88
|
+
try {
|
|
89
|
+
await handle.writeFile(options.content);
|
|
90
|
+
} finally {
|
|
91
|
+
await handle.close();
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
path,
|
|
95
|
+
bytes
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region lib/types/index.js
|
|
100
|
+
/**
|
|
101
|
+
* `LocalSpillStore`: the host-filesystem implementation of the
|
|
102
|
+
* `@deepseek-ai/dsh-spill` storage seam. Persists a tool's oversized text to a
|
|
103
|
+
* private, session-scoped file (see `./store.ts` for the traversal-safe naming
|
|
104
|
+
* and exclusive owner-only write) and returns a path locator plus local
|
|
105
|
+
* read/grep retrieval guidance.
|
|
106
|
+
*
|
|
107
|
+
* @module @deepseek-ai/dsh-spill-local
|
|
108
|
+
*/
|
|
109
|
+
/**
|
|
110
|
+
* Local-filesystem spill backend. Files land under `<root>/session-<hash>/…`
|
|
111
|
+
* with unpredictable names, an exclusive owner-only (0600) write, and a private
|
|
112
|
+
* (0700) root — a spilled tool result must not be readable by other local users
|
|
113
|
+
* or redirectable via a planted symlink.
|
|
114
|
+
*/
|
|
115
|
+
var LocalSpillStore = class extends SpillStore {
|
|
116
|
+
static Config = z.object({ root: z.string() });
|
|
117
|
+
/** Resolved absolute spill root (config `root`, else the private default), fixed at construction. */
|
|
118
|
+
root;
|
|
119
|
+
constructor(ctx, config) {
|
|
120
|
+
super(ctx);
|
|
121
|
+
this.root = config.root !== void 0 ? resolve(config.root) : privateRoot();
|
|
122
|
+
}
|
|
123
|
+
async saveText(input) {
|
|
124
|
+
const saved = await saveTextFile({
|
|
125
|
+
root: this.root,
|
|
126
|
+
sessionId: input.owner.sessionId,
|
|
127
|
+
suggestedName: input.suggestedName,
|
|
128
|
+
content: input.content
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
locator: SpillLocator(saved.path),
|
|
132
|
+
bytes: saved.bytes,
|
|
133
|
+
retrievalHint: "Use read with offset/limit, or grep this path to search within it."
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
138
|
+
export { LocalSpillStore, LocalSpillStore as default, encodeSegment, privateRoot, saveTextFile, sessionDir };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-spill-local`.
|
|
4
|
+
* @module @deepseek-ai/dsh-spill-local/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-spill-local";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "spill-local-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this package exposes no independent event sequence or mutable data relation
|
|
13
|
+
* beyond contracts enforced at its owning seam.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LocalSpillStore`: the host-filesystem implementation of the
|
|
3
|
+
* `@deepseek-ai/dsh-spill` storage seam. Persists a tool's oversized text to a
|
|
4
|
+
* private, session-scoped file (see `./store.ts` for the traversal-safe naming
|
|
5
|
+
* and exclusive owner-only write) and returns a path locator plus local
|
|
6
|
+
* read/grep retrieval guidance.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-spill-local
|
|
9
|
+
*/
|
|
10
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import z from '@deepseek-ai/schemastery';
|
|
12
|
+
import { SpillStore } from '@deepseek-ai/dsh-spill';
|
|
13
|
+
import type { SaveTextSpill, SpillRef } from '@deepseek-ai/dsh-spill';
|
|
14
|
+
export { encodeSegment, privateRoot, saveTextFile, sessionDir } from './store.ts';
|
|
15
|
+
export type { SavedText, SaveTextOptions } from './store.ts';
|
|
16
|
+
/** Plugin config (all optional — `static Config` supplies the defaults). */
|
|
17
|
+
export interface Config {
|
|
18
|
+
/**
|
|
19
|
+
* Root directory for spill files. Omitted uses a lazily-created private
|
|
20
|
+
* (0700) per-process directory under the OS temp dir — the safe default for
|
|
21
|
+
* a local deployment. Set it to keep spill files under a known location.
|
|
22
|
+
*/
|
|
23
|
+
root?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Local-filesystem spill backend. Files land under `<root>/session-<hash>/…`
|
|
27
|
+
* with unpredictable names, an exclusive owner-only (0600) write, and a private
|
|
28
|
+
* (0700) root — a spilled tool result must not be readable by other local users
|
|
29
|
+
* or redirectable via a planted symlink.
|
|
30
|
+
*/
|
|
31
|
+
export declare class LocalSpillStore extends SpillStore {
|
|
32
|
+
static Config: z<Config>;
|
|
33
|
+
/** Resolved absolute spill root (config `root`, else the private default), fixed at construction. */
|
|
34
|
+
readonly root: string;
|
|
35
|
+
constructor(ctx: Context, config: Config);
|
|
36
|
+
saveText(input: SaveTextSpill): Promise<SpillRef>;
|
|
37
|
+
}
|
|
38
|
+
export default LocalSpillStore;
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-spill-local`.
|
|
3
|
+
* @module @deepseek-ai/dsh-spill-local/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "spill-local-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cordis-free storage mechanics for the local spill backend: private
|
|
3
|
+
* session-scoped directory selection, safe-name derivation, path-traversal
|
|
4
|
+
* protection, and the exclusive owner-only write. Kept out of the service class
|
|
5
|
+
* (like `dsh-bash-local`'s `run.ts`) so the filesystem behavior is unit-testable
|
|
6
|
+
* without a `ctx` and without the OS temp dir.
|
|
7
|
+
*
|
|
8
|
+
* @module @deepseek-ai/dsh-spill-local/store
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* The default spill root: a private (0700) per-process directory under the OS
|
|
12
|
+
* tmpdir, created lazily. Predictable world-readable paths would let other
|
|
13
|
+
* local users read spilled tool output or pre-create symlinks; `mkdtemp` gives
|
|
14
|
+
* an unpredictable suffix and 0700 semantics.
|
|
15
|
+
*
|
|
16
|
+
* @returns The lazily-created private spill root.
|
|
17
|
+
*/
|
|
18
|
+
export declare function privateRoot(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Encode an arbitrary string as one safe path segment, injectively over ALL JS
|
|
21
|
+
* (UTF-16) strings. A session id / suggested name is untrusted input, so this
|
|
22
|
+
* neutralizes `../`, absolute paths, NUL, and separators before any filesystem
|
|
23
|
+
* use. Each code unit is kept literal (`[A-Za-z0-9._-]`, minus `~`) or escaped
|
|
24
|
+
* as `~XXXX`; `~` is itself escaped, so the mapping is reversible and distinct
|
|
25
|
+
* inputs never collide. The whole-segment tokens `.`/`..` are escaped so they
|
|
26
|
+
* can never traverse. An empty string encodes to `~` (never an empty segment).
|
|
27
|
+
* (Mirrors the JSONL persistence backend's `encodeSegment`.)
|
|
28
|
+
*
|
|
29
|
+
* @param raw The untrusted string to encode as one safe path segment.
|
|
30
|
+
* @returns An injective, filesystem-safe single path segment.
|
|
31
|
+
*/
|
|
32
|
+
export declare function encodeSegment(raw: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* The session-scoped directory: `<root>/session-<hash(sessionId)>`, a short stable hash.
|
|
35
|
+
*
|
|
36
|
+
* @param root The spill root directory.
|
|
37
|
+
* @param sessionId The owning session id to hash into a stable directory name.
|
|
38
|
+
* @returns The absolute session-scoped spill directory path.
|
|
39
|
+
*/
|
|
40
|
+
export declare function sessionDir(root: string, sessionId: string): string;
|
|
41
|
+
/** Options for {@link saveTextFile} — the resolved root and the request fields the store needs. */
|
|
42
|
+
export interface SaveTextOptions {
|
|
43
|
+
/** The spill root directory (configured or the lazy private default). */
|
|
44
|
+
root: string;
|
|
45
|
+
/** The owning session id (scopes the directory). */
|
|
46
|
+
sessionId: string;
|
|
47
|
+
/** Caller-suggested base name; sanitized to one safe segment before use. */
|
|
48
|
+
suggestedName: string;
|
|
49
|
+
/** The full text to persist. */
|
|
50
|
+
content: string;
|
|
51
|
+
}
|
|
52
|
+
/** A written spill file. */
|
|
53
|
+
export interface SavedText {
|
|
54
|
+
path: string;
|
|
55
|
+
bytes: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Write `content` to a fresh file under the session-scoped directory and return
|
|
59
|
+
* its path + byte length. The filename is a random hex prefix plus the
|
|
60
|
+
* sanitized `suggestedName`, so it is unpredictable (defeats symlink planting in
|
|
61
|
+
* a shared root) AND stays readable. The open is exclusive + owner-only
|
|
62
|
+
* (`'wx', 0o600`): it fails on any existing path — symlink or not — so a
|
|
63
|
+
* pre-planted target cannot redirect the write.
|
|
64
|
+
*
|
|
65
|
+
* @param options The resolved root and request fields required to save the file.
|
|
66
|
+
* @returns The written file path and UTF-8 byte length.
|
|
67
|
+
*/
|
|
68
|
+
export declare function saveTextFile(options: SaveTextOptions): Promise<SavedText>;
|
|
69
|
+
//# sourceMappingURL=store.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-spill-local",
|
|
3
|
+
"description": "Local-filesystem implementation of the DeepSeek Harness spill storage seam (private session-scoped files)",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/spill/spill-local"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "BSD-3-Clause",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
36
|
+
"@deepseek-ai/dsh-spill": "^0.0.1-rc.1",
|
|
37
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@deepseek-ai/dsh-brand": "^0.0.1-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/dsh-spill": "^0.0.1-rc.1",
|
|
48
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
49
|
+
}
|
|
50
|
+
}
|