dsh-plugin-memory-share 0.0.0-stage → 0.4.0
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 +21 -0
- package/README.md +323 -2
- package/bin/status.mjs +79 -0
- package/cordis.patch.yml +4 -0
- package/lib/client.js +20755 -0
- package/lib/index.js +528 -0
- package/lib/remote.js +73 -0
- package/lib/render.js +90 -0
- package/lib/store.js +306 -0
- package/lib/typert.host.js +29 -0
- package/package.json +81 -4
package/lib/store.js
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// dsh-plugin-MemoryShare 存储层:一个进程内可同步读取、磁盘上原子写入的 JSON 记忆库。
|
|
2
|
+
// 设计约束(见 README「设计取舍」):
|
|
3
|
+
// 1. 渲染发生在系统提示词组装里,是同步调用 —— 所以读走内存快照,写才碰磁盘;
|
|
4
|
+
// 2. 多个 DSH 进程可能同时开同一个库 —— 所以写入串行化 + 临时文件 rename;
|
|
5
|
+
// 3. 记忆是长期资产,损坏不能静默丢失 —— 坏文件改名备份后再从空库开始。
|
|
6
|
+
// License: MIT
|
|
7
|
+
import { promises as fs } from 'node:fs'
|
|
8
|
+
import { join, resolve } from 'node:path'
|
|
9
|
+
import { homedir } from 'node:os'
|
|
10
|
+
import { randomBytes } from 'node:crypto'
|
|
11
|
+
|
|
12
|
+
export const STORE_FILE = 'memories.json'
|
|
13
|
+
export const STORE_VERSION = 1
|
|
14
|
+
export const SCOPES = ['user', 'workspace', 'session']
|
|
15
|
+
|
|
16
|
+
/** 淘汰顺序:user 最该保留,session 最先走。 */
|
|
17
|
+
const SCOPE_PRIORITY = { user: 0, workspace: 1, session: 2 }
|
|
18
|
+
const DAY_MS = 86400000
|
|
19
|
+
|
|
20
|
+
/** 默认数据目录:$DSH_HOME/memory-share(DSH 自身的 home)。 */
|
|
21
|
+
export function defaultDataDir() {
|
|
22
|
+
const home = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
23
|
+
return join(home, 'memory-share')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 作用域键归一化:Windows 下大小写不敏感,避免同一目录两条记忆。 */
|
|
27
|
+
export function normalizeKey(value) {
|
|
28
|
+
if (typeof value !== 'string' || value.trim() === '') return ''
|
|
29
|
+
const resolved = resolve(value)
|
|
30
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** 记忆文本归一化:折叠空白、去首尾。 */
|
|
34
|
+
export function normalizeText(value) {
|
|
35
|
+
return String(value ?? '').replace(/\s+/g, ' ').trim()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 作用域键归一化:只有 workspace 是路径,需要 resolve + 大小写归一;
|
|
40
|
+
* session 键是会话 id 字符串,绝不能当路径处理(否则会被 resolve 成磁盘路径)。
|
|
41
|
+
*/
|
|
42
|
+
export function scopeKey(scope, value) {
|
|
43
|
+
if (scope === 'user') return ''
|
|
44
|
+
if (scope === 'workspace') return normalizeKey(value)
|
|
45
|
+
return normalizeText(value)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function fingerprintOf(text) {
|
|
49
|
+
return normalizeText(text).toLowerCase()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** TTL 过期判定(未设置 ttlDays 视为永不过期)。 */
|
|
53
|
+
export function isExpired(entry, now = Date.now()) {
|
|
54
|
+
const ttl = Number(entry?.ttlDays)
|
|
55
|
+
if (!Number.isFinite(ttl) || ttl <= 0) return false
|
|
56
|
+
return now >= (Number(entry.createdAt) || 0) + ttl * DAY_MS
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function newId(entries) {
|
|
60
|
+
const taken = new Set(entries.map((entry) => entry.id))
|
|
61
|
+
// 短 id 让模型更容易原样回引(forget 靠它定位),同时保留时间序 + 随机后缀。
|
|
62
|
+
const stamp = Date.now().toString(36).slice(-6)
|
|
63
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
64
|
+
const id = `m${stamp}${randomBytes(2).toString('hex')}`
|
|
65
|
+
if (!taken.has(id)) return id
|
|
66
|
+
}
|
|
67
|
+
return `m${stamp}${randomBytes(4).toString('hex')}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class MemoryStore {
|
|
71
|
+
/**
|
|
72
|
+
* @param {object} options
|
|
73
|
+
* @param {string} [options.dir] 数据目录
|
|
74
|
+
* @param {number} [options.maxEntries] 条目上限,超出按作用域优先级淘汰
|
|
75
|
+
* @param {{warn?: Function}} [options.logger]
|
|
76
|
+
*/
|
|
77
|
+
constructor({ dir = defaultDataDir(), maxEntries = 500, logger } = {}) {
|
|
78
|
+
this.dir = dir
|
|
79
|
+
this.file = join(dir, STORE_FILE)
|
|
80
|
+
this.maxEntries = Math.max(10, Number(maxEntries) || 500)
|
|
81
|
+
this.logger = logger
|
|
82
|
+
this.entries = []
|
|
83
|
+
this.loaded = false
|
|
84
|
+
this.mtimeMs = -1
|
|
85
|
+
this.chain = Promise.resolve()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
warn(message) {
|
|
89
|
+
try {
|
|
90
|
+
(this.logger?.warn ?? console.warn)(`[memory-share] ${message}`)
|
|
91
|
+
} catch {
|
|
92
|
+
/* 日志失败不能影响主流程 */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 同步快照:供系统提示词渲染使用。 */
|
|
97
|
+
snapshot() {
|
|
98
|
+
return this.entries
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** 读取磁盘;mtime 未变则复用内存。force 时强制重读。 */
|
|
102
|
+
async load(force = false) {
|
|
103
|
+
let stat
|
|
104
|
+
try {
|
|
105
|
+
stat = await fs.stat(this.file)
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error?.code === 'ENOENT') {
|
|
108
|
+
this.entries = []
|
|
109
|
+
this.loaded = true
|
|
110
|
+
this.mtimeMs = 0
|
|
111
|
+
return this.entries
|
|
112
|
+
}
|
|
113
|
+
throw error
|
|
114
|
+
}
|
|
115
|
+
if (!force && this.loaded && stat.mtimeMs === this.mtimeMs) return this.entries
|
|
116
|
+
|
|
117
|
+
const raw = await fs.readFile(this.file, 'utf8')
|
|
118
|
+
let parsed
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(raw)
|
|
121
|
+
} catch {
|
|
122
|
+
const backup = `${this.file}.corrupt-${Date.now()}`
|
|
123
|
+
await fs.rename(this.file, backup).catch(() => {})
|
|
124
|
+
this.warn(`记忆库无法解析,已备份为 ${backup} 并从空库开始`)
|
|
125
|
+
this.entries = []
|
|
126
|
+
this.loaded = true
|
|
127
|
+
this.mtimeMs = 0
|
|
128
|
+
return this.entries
|
|
129
|
+
}
|
|
130
|
+
const list = Array.isArray(parsed?.entries) ? parsed.entries : []
|
|
131
|
+
this.entries = list.filter((entry) => entry && typeof entry.id === 'string' && typeof entry.text === 'string')
|
|
132
|
+
this.loaded = true
|
|
133
|
+
this.mtimeMs = stat.mtimeMs
|
|
134
|
+
return this.entries
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 原子落盘:先写临时文件再 rename,避免读到半个 JSON。 */
|
|
138
|
+
async persist() {
|
|
139
|
+
await fs.mkdir(this.dir, { recursive: true })
|
|
140
|
+
const payload = JSON.stringify({
|
|
141
|
+
version: STORE_VERSION,
|
|
142
|
+
updatedAt: new Date().toISOString(),
|
|
143
|
+
entries: this.entries,
|
|
144
|
+
}, null, 2)
|
|
145
|
+
const tmp = `${this.file}.${process.pid}.${Date.now()}.tmp`
|
|
146
|
+
await fs.writeFile(tmp, payload, 'utf8')
|
|
147
|
+
await fs.rename(tmp, this.file)
|
|
148
|
+
const stat = await fs.stat(this.file)
|
|
149
|
+
this.mtimeMs = stat.mtimeMs
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 串行化的读-改-写。fn 返回 { entries, value }。 */
|
|
153
|
+
async mutate(fn) {
|
|
154
|
+
const run = this.chain.then(async () => {
|
|
155
|
+
await this.load(true)
|
|
156
|
+
const outcome = await fn(this.entries)
|
|
157
|
+
this.entries = outcome.entries
|
|
158
|
+
await this.persist()
|
|
159
|
+
return outcome.value
|
|
160
|
+
})
|
|
161
|
+
this.chain = run.then(() => undefined, () => undefined)
|
|
162
|
+
return run
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** 超出上限时淘汰:先 session,再 workspace,最后 user;同作用域内旧的先走。 */
|
|
166
|
+
evict(entries) {
|
|
167
|
+
if (entries.length <= this.maxEntries) return entries
|
|
168
|
+
const sorted = [...entries].sort((a, b) => {
|
|
169
|
+
const byScope = (SCOPE_PRIORITY[a.scope] ?? 9) - (SCOPE_PRIORITY[b.scope] ?? 9)
|
|
170
|
+
if (byScope !== 0) return byScope
|
|
171
|
+
return (Number(b.updatedAt) || 0) - (Number(a.updatedAt) || 0)
|
|
172
|
+
})
|
|
173
|
+
const keep = new Set(sorted.slice(0, this.maxEntries).map((entry) => entry.id))
|
|
174
|
+
return entries.filter((entry) => keep.has(entry.id))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 保存一条记忆。同一 (文本指纹, scope, key) 视为同一条,更新而不新增。
|
|
179
|
+
* @returns {Promise<{entry: object, duplicate: boolean, total: number}>}
|
|
180
|
+
*/
|
|
181
|
+
async save(input) {
|
|
182
|
+
const text = normalizeText(input?.text)
|
|
183
|
+
if (text === '') throw new Error('memory-share: text 不能为空')
|
|
184
|
+
const scope = SCOPES.includes(input?.scope) ? input.scope : 'user'
|
|
185
|
+
const key = scopeKey(scope, input?.key)
|
|
186
|
+
if (scope !== 'user' && key === '') {
|
|
187
|
+
throw new Error(`memory-share: scope=${scope} 需要一个可用的键(工作区路径或会话 id)`)
|
|
188
|
+
}
|
|
189
|
+
const tags = Array.isArray(input?.tags)
|
|
190
|
+
? [...new Set(input.tags.map((tag) => normalizeText(tag)).filter(Boolean))].slice(0, 12)
|
|
191
|
+
: []
|
|
192
|
+
const ttlDays = Number.isFinite(Number(input?.ttlDays)) && Number(input.ttlDays) > 0
|
|
193
|
+
? Math.floor(Number(input.ttlDays))
|
|
194
|
+
: undefined
|
|
195
|
+
const fingerprint = fingerprintOf(text)
|
|
196
|
+
|
|
197
|
+
return this.mutate((entries) => {
|
|
198
|
+
const now = Date.now()
|
|
199
|
+
const existing = entries.find((entry) => entry.fingerprint === fingerprint
|
|
200
|
+
&& entry.scope === scope
|
|
201
|
+
&& (entry.key ?? '') === key)
|
|
202
|
+
if (existing !== undefined) {
|
|
203
|
+
existing.updatedAt = now
|
|
204
|
+
if (tags.length > 0) existing.tags = [...new Set([...(existing.tags ?? []), ...tags])]
|
|
205
|
+
if (ttlDays !== undefined) existing.ttlDays = ttlDays
|
|
206
|
+
return { entries, value: { entry: existing, duplicate: true, total: entries.length } }
|
|
207
|
+
}
|
|
208
|
+
const entry = {
|
|
209
|
+
id: newId(entries),
|
|
210
|
+
text,
|
|
211
|
+
scope,
|
|
212
|
+
key,
|
|
213
|
+
fingerprint,
|
|
214
|
+
tags,
|
|
215
|
+
createdAt: now,
|
|
216
|
+
updatedAt: now,
|
|
217
|
+
source: input?.source ?? {},
|
|
218
|
+
}
|
|
219
|
+
if (ttlDays !== undefined) entry.ttlDays = ttlDays
|
|
220
|
+
entries.push(entry)
|
|
221
|
+
return { entries: this.evict(entries), value: { entry, duplicate: false, total: entries.length } }
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 检索。query 为空时返回最近的条目;否则按命中词数排序。
|
|
227
|
+
* @returns {Promise<object[]>}
|
|
228
|
+
*/
|
|
229
|
+
async list({ scope, key, query, limit = 20, includeExpired = false } = {}) {
|
|
230
|
+
await this.load()
|
|
231
|
+
const now = Date.now()
|
|
232
|
+
const max = Math.max(1, Math.min(200, Number(limit) || 20))
|
|
233
|
+
const tokens = normalizeText(query).toLowerCase().split(/\s+/).filter(Boolean)
|
|
234
|
+
const wantedKey = scope === undefined ? '' : scopeKey(scope, key)
|
|
235
|
+
|
|
236
|
+
let rows = this.entries.filter((entry) => {
|
|
237
|
+
if (!includeExpired && isExpired(entry, now)) return false
|
|
238
|
+
if (scope !== undefined && entry.scope !== scope) return false
|
|
239
|
+
if (scope && scope !== 'user' && (entry.key ?? '') !== wantedKey) return false
|
|
240
|
+
return true
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
if (tokens.length > 0) {
|
|
244
|
+
const scored = []
|
|
245
|
+
for (const entry of rows) {
|
|
246
|
+
const haystack = `${entry.text} ${(entry.tags ?? []).join(' ')}`.toLowerCase()
|
|
247
|
+
const score = tokens.reduce((sum, token) => (haystack.includes(token) ? sum + 1 : sum), 0)
|
|
248
|
+
if (score > 0) scored.push({ entry, score })
|
|
249
|
+
}
|
|
250
|
+
scored.sort((a, b) => b.score - a.score || (Number(b.entry.updatedAt) || 0) - (Number(a.entry.updatedAt) || 0))
|
|
251
|
+
return scored.slice(0, max).map((item) => item.entry)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
rows = rows.sort((a, b) => (Number(b.updatedAt) || 0) - (Number(a.updatedAt) || 0))
|
|
255
|
+
return rows.slice(0, max)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** 删除一条。返回是否真的删掉了。 */
|
|
259
|
+
async remove(id) {
|
|
260
|
+
const target = normalizeText(id)
|
|
261
|
+
if (target === '') throw new Error('memory-share: id 不能为空')
|
|
262
|
+
return this.mutate((entries) => {
|
|
263
|
+
const next = entries.filter((entry) => entry.id !== target)
|
|
264
|
+
return { entries: next, value: next.length !== entries.length }
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** 修改一条的正文。指纹同步更新,避免改完还能被旧指纹命中去重。 */
|
|
269
|
+
async update(id, text) {
|
|
270
|
+
const target = normalizeText(id)
|
|
271
|
+
if (target === '') throw new Error('memory-share: id 不能为空')
|
|
272
|
+
const nextText = normalizeText(text)
|
|
273
|
+
if (nextText === '') throw new Error('memory-share: text 不能为空')
|
|
274
|
+
return this.mutate((entries) => {
|
|
275
|
+
const entry = entries.find((item) => item.id === target)
|
|
276
|
+
if (entry === undefined) return { entries, value: false }
|
|
277
|
+
entry.text = nextText
|
|
278
|
+
entry.fingerprint = fingerprintOf(nextText)
|
|
279
|
+
entry.updatedAt = Date.now()
|
|
280
|
+
return { entries, value: true }
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** 清空某个作用域(user 即清空全部 user 级记忆)。返回删除条数。 */
|
|
285
|
+
async clear(scope, key) {
|
|
286
|
+
if (!SCOPES.includes(scope)) throw new Error(`memory-share: 未知 scope ${scope}`)
|
|
287
|
+
const wantedKey = scopeKey(scope, key)
|
|
288
|
+
return this.mutate((entries) => {
|
|
289
|
+
const next = entries.filter((entry) => !(entry.scope === scope && (entry.key ?? '') === wantedKey))
|
|
290
|
+
return { entries: next, value: entries.length - next.length }
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** 同步筛选出对某个 agent 可见的记忆(渲染热路径)。 */
|
|
295
|
+
selectFor({ cwd, sessionId, now = Date.now() } = {}) {
|
|
296
|
+
const workspaceKey = normalizeKey(cwd)
|
|
297
|
+
const sessionKey = normalizeText(sessionId)
|
|
298
|
+
return this.entries.filter((entry) => {
|
|
299
|
+
if (isExpired(entry, now)) return false
|
|
300
|
+
if (entry.scope === 'user') return true
|
|
301
|
+
if (entry.scope === 'workspace') return workspaceKey !== '' && entry.key === workspaceKey
|
|
302
|
+
if (entry.scope === 'session') return sessionKey !== '' && entry.key === sessionKey
|
|
303
|
+
return false
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// 严格反射清单,由 DSH Typert 加载器校验。结构对齐 dsh-plugin-mind-tree。
|
|
2
|
+
// License: MIT
|
|
3
|
+
import { PACKAGE, NAMESPACE, LIST_DESCRIPTOR, UPDATE_DESCRIPTOR, REMOVE_DESCRIPTOR } from './remote.js'
|
|
4
|
+
|
|
5
|
+
export const TYPERT = {
|
|
6
|
+
package: PACKAGE,
|
|
7
|
+
face: 'host',
|
|
8
|
+
schemas: [],
|
|
9
|
+
invocations: [LIST_DESCRIPTOR, UPDATE_DESCRIPTOR, REMOVE_DESCRIPTOR],
|
|
10
|
+
model: {
|
|
11
|
+
services: [{
|
|
12
|
+
key: NAMESPACE,
|
|
13
|
+
exportName: 'MemoryShareService',
|
|
14
|
+
description: '只读为主的管理面:列出全部长期记忆条目、修改单条正文、删除单条。写入记忆仍走 memory_save 工具,不在本服务上暴露。',
|
|
15
|
+
summary: '跨会话长期记忆的查看与维护',
|
|
16
|
+
tags: ['memory', 'long-term-memory', 'settings'],
|
|
17
|
+
members: [
|
|
18
|
+
{ kind: 'method', name: 'list', signature: 'list(): Promise<MemoryList>' },
|
|
19
|
+
{ kind: 'method', name: 'update', signature: 'update(id: string, text: string): Promise<MemoryMutation>' },
|
|
20
|
+
{ kind: 'method', name: 'forget', signature: 'forget(id: string): Promise<MemoryMutation>' },
|
|
21
|
+
],
|
|
22
|
+
types: [],
|
|
23
|
+
}],
|
|
24
|
+
events: [],
|
|
25
|
+
objects: [],
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default TYPERT
|
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-memory-share",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "dsh-plugin-MemoryShare — 跨会话共享的长期记忆:被动注入系统提示词,配 memory_save / memory_recall / memory_forget 三个工具,以及「设置 → 插件 → 记忆」管理页。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "ShOpenMind",
|
|
9
|
+
"url": "https://github.com/ShOpenMind"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/ShOpenMind/dsh-plugin-MemoryShare.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/ShOpenMind/dsh-plugin-MemoryShare#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/ShOpenMind/dsh-plugin-MemoryShare/issues"
|
|
18
|
+
},
|
|
19
|
+
"main": "lib/index.js",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./lib/index.js",
|
|
22
|
+
"./remote": "./lib/remote.js",
|
|
23
|
+
"./typert": "./lib/typert.host.js",
|
|
24
|
+
"./client": "./lib/client.js",
|
|
25
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib",
|
|
30
|
+
"bin",
|
|
31
|
+
"cordis.patch.yml",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"keywords": [
|
|
36
|
+
"dsh",
|
|
37
|
+
"deepseek-harness",
|
|
38
|
+
"plugin",
|
|
39
|
+
"memory",
|
|
40
|
+
"long-term-memory",
|
|
41
|
+
"dsh-plugin",
|
|
42
|
+
"dsh-bundle",
|
|
43
|
+
"dsh-market",
|
|
44
|
+
"agent-memory"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "node tools/build.mjs",
|
|
48
|
+
"test": "node tools/host-test.mjs && node tools/cordis-mount-test.mjs",
|
|
49
|
+
"test:host": "node tools/host-test.mjs",
|
|
50
|
+
"test:cordis": "node tools/cordis-mount-test.mjs",
|
|
51
|
+
"demo": "node tools/demo.mjs"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=22"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"zod": "^4.4.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"esbuild": "^0.25.0"
|
|
61
|
+
},
|
|
62
|
+
"dsh": {
|
|
63
|
+
"bundle": {
|
|
64
|
+
"patch": "./cordis.patch.yml"
|
|
65
|
+
},
|
|
66
|
+
"client": {
|
|
67
|
+
"platform": "web",
|
|
68
|
+
"inject": [
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
70
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
71
|
+
"@deepseek-ai/dsh-api-gateway"
|
|
72
|
+
],
|
|
73
|
+
"external": [
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
76
|
+
"@deepseek-ai/dsh-api-gateway"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"bin": {
|
|
81
|
+
"dsh-plugin-memory-share": "bin/status.mjs"
|
|
82
|
+
}
|
|
83
|
+
}
|