@lmzhen/dsh-memory-files 0.16.0 → 0.17.0-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/lib/index.js +145 -64
- package/lib/types/index.d.ts +40 -27
- package/package.json +7 -7
package/lib/index.js
CHANGED
|
@@ -1,34 +1,53 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
-
import { DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_USER_CHAR_LIMIT, MemoryStore,
|
|
4
|
+
import { DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_USER_CHAR_LIMIT, MemoryStore, clampedNumber, evolutionIoAdapter, makeSerialQueue, paramRowId } from "@lmzhen/dsh-evolution-core";
|
|
5
5
|
//#region lib/types/index.js
|
|
6
6
|
/**
|
|
7
7
|
* Local layered memory provider for `ctx.memory`.
|
|
8
8
|
* @module @lmzhen/dsh-memory-files
|
|
9
9
|
*/
|
|
10
|
-
const MEMORY_SETTINGS_SCHEMA = z.object({
|
|
11
|
-
memoryChars: z.number().min(1).default(DEFAULT_MEMORY_CHAR_LIMIT),
|
|
12
|
-
userChars: z.number().min(1).default(DEFAULT_USER_CHAR_LIMIT),
|
|
13
|
-
addDatePrefix: z.boolean().default(false),
|
|
14
|
-
maxConsolidationFailures: z.number().min(1).default(DEFAULT_CONSOLIDATION_FAILURES)
|
|
15
|
-
});
|
|
16
10
|
const name = "memory-files";
|
|
17
11
|
const inject = ["memory", "evolutionIo"];
|
|
18
12
|
const Config = z.object({
|
|
19
13
|
providerName: z.string().default("files"),
|
|
14
|
+
memoryChars: z.number().min(1).volatile(),
|
|
15
|
+
userChars: z.number().min(1).volatile(),
|
|
20
16
|
memoryCharLimit: z.number().min(1).default(DEFAULT_MEMORY_CHAR_LIMIT),
|
|
21
17
|
userCharLimit: z.number().min(1).default(DEFAULT_USER_CHAR_LIMIT),
|
|
22
|
-
addDatePrefix: z.boolean().default(false),
|
|
18
|
+
addDatePrefix: z.boolean().default(false).volatile(),
|
|
23
19
|
root: z.string().default(""),
|
|
24
|
-
maxConsolidationFailures: z.number().min(1).default(DEFAULT_CONSOLIDATION_FAILURES),
|
|
20
|
+
maxConsolidationFailures: z.number().min(1).default(DEFAULT_CONSOLIDATION_FAILURES).volatile(),
|
|
25
21
|
threatExemptLabels: z.array(z.string()).default([])
|
|
26
22
|
});
|
|
27
23
|
function apply(ctx, rawConfig = {}) {
|
|
28
|
-
|
|
24
|
+
ctx.inject(["settings"], (injected) => {
|
|
25
|
+
const settings = injected.settings;
|
|
26
|
+
if (typeof settings?.configure !== "function") return;
|
|
27
|
+
const disposer = settings.configure({ auto: false });
|
|
28
|
+
if (typeof disposer === "function") ctx.effect(() => disposer, "memory-files: settings presentation");
|
|
29
|
+
});
|
|
30
|
+
const clampedKeys = /* @__PURE__ */ new Set();
|
|
31
|
+
/**
|
|
32
|
+
* One live numeric row field as the platform resolved it.
|
|
33
|
+
* @param value - the field, or its volatile reference.
|
|
34
|
+
* @returns the current plain value, or undefined when nothing supplied one.
|
|
35
|
+
*/
|
|
36
|
+
const liveNumber = (value) => typeof value === "object" ? value.get() : value;
|
|
37
|
+
/**
|
|
38
|
+
* Clamp one supplied numeric value, warning once per key.
|
|
39
|
+
* @param name - the config key, for the warning.
|
|
40
|
+
* @param value - the value as supplied by the deployment or the user.
|
|
41
|
+
* @param fallback - the package default a corrected value falls back to.
|
|
42
|
+
* @returns the value the store may use.
|
|
43
|
+
*/
|
|
29
44
|
const field = (name, value, fallback) => {
|
|
30
|
-
const
|
|
31
|
-
|
|
45
|
+
const current = liveNumber(value);
|
|
46
|
+
const result = clampedNumber(current, fallback, { min: 1 });
|
|
47
|
+
if (current !== void 0 && result !== current && !clampedKeys.has(name)) {
|
|
48
|
+
clampedKeys.add(name);
|
|
49
|
+
ctx.logger.warn(`memory-files: ${name} provided an invalid value; falling back to the default`);
|
|
50
|
+
}
|
|
32
51
|
return result;
|
|
33
52
|
};
|
|
34
53
|
const floorLimit = (value) => Math.floor(value);
|
|
@@ -39,58 +58,99 @@ function apply(ctx, rawConfig = {}) {
|
|
|
39
58
|
user: snapshot?.userChars
|
|
40
59
|
};
|
|
41
60
|
};
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
const aliasDefaults = Config["~standard"].validate({}).value;
|
|
62
|
+
const ALIAS_OF = {
|
|
63
|
+
memoryChars: "memoryCharLimit",
|
|
64
|
+
userChars: "userCharLimit"
|
|
65
|
+
};
|
|
66
|
+
const DEFAULTS = {
|
|
67
|
+
memoryChars: DEFAULT_MEMORY_CHAR_LIMIT,
|
|
68
|
+
userChars: DEFAULT_USER_CHAR_LIMIT
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* The live value of one canonical budget key (G1 §8.3).
|
|
72
|
+
* @param id - the canonical registry id.
|
|
73
|
+
* @returns the value the platform resolved (user layer included), or undefined.
|
|
74
|
+
*/
|
|
75
|
+
const canonicalBudget = (id) => liveNumber(id === "memoryChars" ? rawConfig.memoryChars : rawConfig.userChars);
|
|
76
|
+
/**
|
|
77
|
+
* Whether the OPERATOR set this budget on THIS ROW (G0/S0.4 + G1).
|
|
78
|
+
*
|
|
79
|
+
* The canonical key answers through its live reference — a user-layer value
|
|
80
|
+
* arrives the same way, which is what keeps a user choice ahead of the policy —
|
|
81
|
+
* and the deprecated alias through the default comparison above. The pair is
|
|
82
|
+
* documented in the Config JSDoc and reported by /evolution doctor (budgetIssues).
|
|
83
|
+
* @param id - the canonical registry id.
|
|
84
|
+
* @returns whether the row or the user supplied a value.
|
|
85
|
+
*/
|
|
86
|
+
const explicitBudget = (id) => {
|
|
87
|
+
if (canonicalBudget(id) !== void 0) return true;
|
|
88
|
+
const alias = rawConfig[ALIAS_OF[id]];
|
|
89
|
+
return alias !== void 0 && alias !== aliasDefaults[ALIAS_OF[id]];
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* The budget one key resolves to (G1 §8.4-A): the row's own value — canonical or
|
|
93
|
+
* alias — over the mounted policy, over the package default.
|
|
94
|
+
* @param id - the canonical registry id.
|
|
95
|
+
* @returns the resolved, unclamped budget.
|
|
96
|
+
*/
|
|
97
|
+
const budget = (id) => {
|
|
98
|
+
const explicit = canonicalBudget(id) ?? (explicitBudget(id) ? rawConfig[ALIAS_OF[id]] : void 0);
|
|
99
|
+
if (explicit !== void 0) return explicit;
|
|
100
|
+
const policy = policyBudget();
|
|
101
|
+
return (id === "memoryChars" ? policy.memory : policy.user) ?? DEFAULTS[id];
|
|
102
|
+
};
|
|
103
|
+
const memoryChars = () => floorLimit(field("memoryCharLimit", budget("memoryChars"), DEFAULT_MEMORY_CHAR_LIMIT));
|
|
104
|
+
const userChars = () => floorLimit(field("userCharLimit", budget("userChars"), DEFAULT_USER_CHAR_LIMIT));
|
|
105
|
+
const consolidationFailures = () => field("maxConsolidationFailures", rawConfig.maxConsolidationFailures, DEFAULT_CONSOLIDATION_FAILURES);
|
|
106
|
+
const addDatePrefix = () => rawConfig.addDatePrefix?.get() ?? false;
|
|
107
|
+
const policyAtMount = policyBudget();
|
|
108
|
+
if (explicitBudget("memoryChars") && policyAtMount.memory !== void 0 && memoryChars() !== policyAtMount.memory) ctx.logger.warn(`memory-files: the row's memoryChars=${memoryChars()} contradicts evolution-policy memoryChars=${policyAtMount.memory} — the store enforces the row value while the review plans against the policy value; align them or leave the row unset`);
|
|
109
|
+
if (explicitBudget("userChars") && policyAtMount.user !== void 0 && userChars() !== policyAtMount.user) ctx.logger.warn(`memory-files: the row's userChars=${userChars()} contradicts evolution-policy userChars=${policyAtMount.user} — same divergence as memoryChars`);
|
|
53
110
|
const io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
54
111
|
const serializedWrite = makeSerialQueue();
|
|
55
|
-
const trimmedRoot = (
|
|
112
|
+
const trimmedRoot = (rawConfig.root || "").trim();
|
|
56
113
|
const resolvedRoot = trimmedRoot === "" ? "" : resolve(trimmedRoot);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
114
|
+
let store;
|
|
115
|
+
let storeKey = "";
|
|
116
|
+
/**
|
|
117
|
+
* The store for the configuration as it stands right now.
|
|
118
|
+
* @returns the current store, rebuilt when a resolved limit changed.
|
|
119
|
+
*/
|
|
120
|
+
const currentStore = () => {
|
|
121
|
+
const options = {
|
|
122
|
+
memoryCharLimit: memoryChars(),
|
|
123
|
+
userCharLimit: userChars(),
|
|
124
|
+
addDatePrefix: addDatePrefix(),
|
|
125
|
+
maxConsolidationFailures: consolidationFailures()
|
|
126
|
+
};
|
|
127
|
+
const key = `${options.memoryCharLimit}|${options.userCharLimit}|${options.addDatePrefix}|${options.maxConsolidationFailures}`;
|
|
128
|
+
if (store !== void 0 && key === storeKey) return store;
|
|
129
|
+
store = new MemoryStore({
|
|
130
|
+
...options,
|
|
131
|
+
...rawConfig.threatExemptLabels !== void 0 ? { threatExemptLabels: rawConfig.threatExemptLabels } : {},
|
|
132
|
+
...resolvedRoot !== "" ? { root: resolvedRoot } : {},
|
|
133
|
+
io
|
|
134
|
+
});
|
|
135
|
+
storeKey = key;
|
|
136
|
+
return store;
|
|
137
|
+
};
|
|
81
138
|
const provider = {
|
|
82
|
-
name:
|
|
83
|
-
read: (target) =>
|
|
139
|
+
name: rawConfig.providerName ?? "files",
|
|
140
|
+
read: (target) => currentStore().read(target),
|
|
84
141
|
applyBatch: async (target, operations) => {
|
|
85
142
|
const normalized = operations.map((op) => ({
|
|
86
143
|
action: op.action,
|
|
87
144
|
facts: op.facts ?? op.content,
|
|
88
145
|
old_text: op.old_text
|
|
89
146
|
}));
|
|
90
|
-
return await serializedWrite(() =>
|
|
147
|
+
return await serializedWrite(() => currentStore().applyBatch(target, normalized));
|
|
91
148
|
},
|
|
92
149
|
snapshot: async () => {
|
|
93
|
-
const [memory, user] = await serializedWrite(async () =>
|
|
150
|
+
const [memory, user] = await serializedWrite(async () => {
|
|
151
|
+
const current = currentStore();
|
|
152
|
+
return [await current.read("memory"), await current.read("user")];
|
|
153
|
+
});
|
|
94
154
|
const text = JSON.stringify([memory, user]);
|
|
95
155
|
return {
|
|
96
156
|
version: 1,
|
|
@@ -99,30 +159,51 @@ function apply(ctx, rawConfig = {}) {
|
|
|
99
159
|
user
|
|
100
160
|
};
|
|
101
161
|
},
|
|
102
|
-
renderContext: () => serializedWrite(() =>
|
|
162
|
+
renderContext: () => serializedWrite(() => currentStore().renderContext())
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* The keys the user set in this row's namespace (G1 §8.4-A). This is the ONLY
|
|
166
|
+
* remaining read of the settings user layer, and it reads KEY NAMES, never
|
|
167
|
+
* values: the platform resolves the user layer into the row's live fields, so the
|
|
168
|
+
* value comes from `budget` — but naming the winning surface needs to know
|
|
169
|
+
* whether the user set the key at all.
|
|
170
|
+
* @returns the keys present in this row's user layer.
|
|
171
|
+
*/
|
|
172
|
+
const userSetKeys = () => {
|
|
173
|
+
const entry = ctx.get("settings")?.describe?.({ redactSecrets: false }).find((item) => item.ns === paramRowId("memory-files"));
|
|
174
|
+
return new Set(Object.keys(entry?.user ?? {}));
|
|
103
175
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Which surface supplied one budget.
|
|
178
|
+
* @param id - the canonical registry id.
|
|
179
|
+
* @returns 'user', 'config', 'policy' or 'default'.
|
|
180
|
+
*/
|
|
181
|
+
const limitSource = (id) => {
|
|
182
|
+
if (userSetKeys().has(id)) return "user";
|
|
183
|
+
if (explicitBudget(id)) return "config";
|
|
184
|
+
return (id === "memoryChars" ? policyBudget().memory : policyBudget().user) === void 0 ? "default" : "policy";
|
|
108
185
|
};
|
|
109
186
|
ctx.provide("evolutionMemoryBudget", {
|
|
110
187
|
get memoryCharLimit() {
|
|
111
|
-
return
|
|
188
|
+
return memoryChars();
|
|
112
189
|
},
|
|
113
190
|
get userCharLimit() {
|
|
114
|
-
return
|
|
191
|
+
return userChars();
|
|
115
192
|
},
|
|
116
193
|
get memorySource() {
|
|
117
|
-
return limitSource("memoryChars"
|
|
194
|
+
return limitSource("memoryChars");
|
|
118
195
|
},
|
|
119
196
|
get userSource() {
|
|
120
|
-
return limitSource("userChars"
|
|
197
|
+
return limitSource("userChars");
|
|
121
198
|
},
|
|
122
|
-
policyMemoryChars
|
|
123
|
-
|
|
199
|
+
get policyMemoryChars() {
|
|
200
|
+
return policyBudget().memory;
|
|
201
|
+
},
|
|
202
|
+
get policyUserChars() {
|
|
203
|
+
return policyBudget().user;
|
|
204
|
+
}
|
|
124
205
|
});
|
|
125
206
|
ctx.effect(() => ctx.memory.registerProvider(provider), "memory-files.provider");
|
|
126
207
|
}
|
|
127
208
|
//#endregion
|
|
128
|
-
export { Config,
|
|
209
|
+
export { Config, apply, inject, name };
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,46 +2,59 @@
|
|
|
2
2
|
* Local layered memory provider for `ctx.memory`.
|
|
3
3
|
* @module @lmzhen/dsh-memory-files
|
|
4
4
|
*/
|
|
5
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
5
|
+
import type { Context, Volatile } from '@deepseek-ai/cordis';
|
|
6
6
|
import z from '@deepseek-ai/schemastery';
|
|
7
|
-
/** G3/S3.2: the memory section's schema. Field names are the CANONICAL ids from
|
|
8
|
-
* the parameter registry (`memoryChars`/`userChars` are the policy-side names the
|
|
9
|
-
* review planner reads too), so the settings document, the params output, the
|
|
10
|
-
* doctor report and the cards spell one name. */
|
|
11
|
-
export interface MemorySettings {
|
|
12
|
-
/** Character budget the store enforces for MEMORY.md. */
|
|
13
|
-
memoryChars: number;
|
|
14
|
-
/** Character budget the store enforces for USER.md. */
|
|
15
|
-
userChars: number;
|
|
16
|
-
/** Prefix stored entries with their date heading. */
|
|
17
|
-
addDatePrefix: boolean;
|
|
18
|
-
/** Consolidation failures one turn tolerates before the tool stops retrying. */
|
|
19
|
-
maxConsolidationFailures: number;
|
|
20
|
-
}
|
|
21
|
-
export declare const MEMORY_SETTINGS_SCHEMA: z<MemorySettings>;
|
|
22
7
|
export declare const name = "memory-files";
|
|
23
8
|
export declare const inject: string[];
|
|
24
9
|
export interface Config {
|
|
25
10
|
providerName?: string;
|
|
26
|
-
/** Store-enforced character budget for MEMORY.md
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `/evolution doctor` reports the divergence (budgetIssues).
|
|
30
|
-
*
|
|
11
|
+
/** Store-enforced character budget for MEMORY.md — the CANONICAL registry id
|
|
12
|
+
* (G1) and a LIVE row field (registry tier E3). The policy row's `memoryChars`
|
|
13
|
+
* is the same semantic on the review-planner side; the two are NOT
|
|
14
|
+
* auto-synchronised — `/evolution doctor` reports the divergence (budgetIssues).
|
|
15
|
+
* A row may spell either this name or the deprecated alias below. No schema
|
|
16
|
+
* default: the alias carries it, and a default on the canonical key would fill
|
|
17
|
+
* it and leave the alias unreachable (the store resolves
|
|
18
|
+
* DEFAULT_MEMORY_CHAR_LIMIT after the alias fallback). */
|
|
19
|
+
memoryChars?: Volatile<number | undefined>;
|
|
20
|
+
/** Store-enforced character budget for USER.md — the CANONICAL registry id (G1),
|
|
21
|
+
* a live row field on the same terms as `memoryChars`. */
|
|
22
|
+
userChars?: Volatile<number | undefined>;
|
|
23
|
+
/** Deprecated alias of `memoryChars` (G0/S0.3): still readable, refused by
|
|
24
|
+
* writes; removed in 0.7.0. */
|
|
31
25
|
memoryCharLimit?: number;
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
* alias (G0/S0.3): still readable, refused by writes; removed in 0.7.0. */
|
|
26
|
+
/** Deprecated alias of `userChars` (G0/S0.3): still readable, refused by
|
|
27
|
+
* writes; removed in 0.7.0. */
|
|
35
28
|
userCharLimit?: number;
|
|
36
|
-
addDatePrefix?: boolean
|
|
29
|
+
addDatePrefix?: Volatile<boolean>;
|
|
37
30
|
root?: string;
|
|
38
31
|
/** How many consolidation failures one turn tolerates before the tool tells the model to stop retrying. */
|
|
39
|
-
maxConsolidationFailures?: number
|
|
32
|
+
maxConsolidationFailures?: Volatile<number>;
|
|
40
33
|
/** F-2 (v18): deployment-declared benign threat labels for the MEMORY store.
|
|
41
34
|
* The skill/guard channels already read this option; without it the
|
|
42
35
|
* README's "MemoryStore store option" exemption was unreachable. */
|
|
43
36
|
threatExemptLabels?: string[];
|
|
44
37
|
}
|
|
45
|
-
export declare const Config: z<
|
|
38
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
39
|
+
providerName: z<string, string, "defined">;
|
|
40
|
+
memoryChars: z<number, number, "volatile">;
|
|
41
|
+
userChars: z<number, number, "volatile">;
|
|
42
|
+
memoryCharLimit: z<number, number, "defined">;
|
|
43
|
+
userCharLimit: z<number, number, "defined">;
|
|
44
|
+
addDatePrefix: z<boolean, boolean, "volatile-defined">;
|
|
45
|
+
root: z<string, string, "defined">;
|
|
46
|
+
maxConsolidationFailures: z<number, number, "volatile-defined">;
|
|
47
|
+
threatExemptLabels: z<string[], string[], "defined">;
|
|
48
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
49
|
+
providerName: z<string, string, "defined">;
|
|
50
|
+
memoryChars: z<number, number, "volatile">;
|
|
51
|
+
userChars: z<number, number, "volatile">;
|
|
52
|
+
memoryCharLimit: z<number, number, "defined">;
|
|
53
|
+
userCharLimit: z<number, number, "defined">;
|
|
54
|
+
addDatePrefix: z<boolean, boolean, "volatile-defined">;
|
|
55
|
+
root: z<string, string, "defined">;
|
|
56
|
+
maxConsolidationFailures: z<number, number, "volatile-defined">;
|
|
57
|
+
threatExemptLabels: z<string[], string[], "defined">;
|
|
58
|
+
}>>, "plain">;
|
|
46
59
|
export declare function apply(ctx: Context, rawConfig?: Config): void;
|
|
47
60
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-memory-files",
|
|
3
3
|
"description": "Local layered memory provider (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.17.0-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -27,16 +27,16 @@
|
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
30
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
30
|
+
"@lmzhen/dsh-evolution-core": "^0.17.0-rc.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
34
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
35
|
-
"@lmzhen/dsh-memory": "^0.
|
|
34
|
+
"@lmzhen/dsh-evolution-io": "^0.17.0-rc.1",
|
|
35
|
+
"@lmzhen/dsh-memory": "^0.17.0-rc.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
39
|
-
"@lmzhen/dsh-evolution-io": "^0.
|
|
40
|
-
"@lmzhen/dsh-memory": "^0.
|
|
38
|
+
"@lmzhen/dsh-evolution-core": "^0.17.0-rc.1",
|
|
39
|
+
"@lmzhen/dsh-evolution-io": "^0.17.0-rc.1",
|
|
40
|
+
"@lmzhen/dsh-memory": "^0.17.0-rc.1"
|
|
41
41
|
}
|
|
42
42
|
}
|