@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 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.
@@ -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 };
@@ -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
+ }