@goodandready/dsh-messenger-gateway 0.3.21 → 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/lib/text.js CHANGED
@@ -1,39 +1,39 @@
1
- export function splitText(text, maxLen) {
2
- const raw = String(text ?? '')
3
- if (raw.length <= maxLen) return [raw]
4
- const chunks = []
5
- let rest = raw
6
- while (rest.length > maxLen) {
7
- let cut = rest.lastIndexOf('\n', maxLen)
8
- if (cut < maxLen / 2) cut = rest.lastIndexOf(' ', maxLen)
9
- if (cut < maxLen / 2) cut = maxLen
10
- chunks.push(rest.slice(0, cut).trim())
11
- rest = rest.slice(cut).trimStart()
12
- }
13
- if (rest.length > 0) chunks.push(rest)
14
- return chunks
15
- }
16
-
17
- export function assistantText(message) {
18
- return (message?.content || [])
19
- .filter((block) => block.type === 'text')
20
- .map((block) => block.text)
21
- .join('')
22
- }
23
-
24
-
25
- /** Drop English reasoning preamble before the user-facing reply (Telegram). */
26
- export function stripReasoningPreamble(text) {
27
- const raw = String(text ?? '').trim()
28
- if (!raw) return raw
29
- const paragraphs = raw.split(/\n\n+/)
30
- for (let i = 0; i < paragraphs.length; i++) {
31
- const p = paragraphs[i]
32
- const cyrillic = (p.match(/[\u0400-\u04FF]/g) || []).length
33
- const latin = (p.match(/[A-Za-z]/g) || []).length
34
- if (cyrillic >= 12 && cyrillic > latin) return paragraphs.slice(i).join('\n\n').trim()
35
- }
36
- return raw
37
- }
38
-
39
- export const MESSENGER_RELAY_INSTRUCTION = 'Channel: Messenger. Reply concisely and directly to the user. Do not leak internal reasoning or planning traces.'
1
+ export function splitText(text, maxLen) {
2
+ const raw = String(text ?? '')
3
+ if (raw.length <= maxLen) return [raw]
4
+ const chunks = []
5
+ let rest = raw
6
+ while (rest.length > maxLen) {
7
+ let cut = rest.lastIndexOf('\n', maxLen)
8
+ if (cut < maxLen / 2) cut = rest.lastIndexOf(' ', maxLen)
9
+ if (cut < maxLen / 2) cut = maxLen
10
+ chunks.push(rest.slice(0, cut).trim())
11
+ rest = rest.slice(cut).trimStart()
12
+ }
13
+ if (rest.length > 0) chunks.push(rest)
14
+ return chunks
15
+ }
16
+
17
+ export function assistantText(message) {
18
+ return (message?.content || [])
19
+ .filter((block) => block.type === 'text')
20
+ .map((block) => block.text)
21
+ .join('')
22
+ }
23
+
24
+
25
+ /** Drop English reasoning preamble before the user-facing reply (Telegram). */
26
+ export function stripReasoningPreamble(text) {
27
+ const raw = String(text ?? '').trim()
28
+ if (!raw) return raw
29
+ const paragraphs = raw.split(/\n\n+/)
30
+ for (let i = 0; i < paragraphs.length; i++) {
31
+ const p = paragraphs[i]
32
+ const cyrillic = (p.match(/[\u0400-\u04FF]/g) || []).length
33
+ const latin = (p.match(/[A-Za-z]/g) || []).length
34
+ if (cyrillic >= 12 && cyrillic > latin) return paragraphs.slice(i).join('\n\n').trim()
35
+ }
36
+ return raw
37
+ }
38
+
39
+ export const MESSENGER_RELAY_INSTRUCTION = 'Channel: Messenger. Reply concisely and directly to the user. Do not leak internal reasoning or planning traces.'
package/lib/topics.js CHANGED
@@ -25,20 +25,6 @@ export function sessionKey({ platform, chatId, threadId = 0, userId, chatType, s
25
25
  return base
26
26
  }
27
27
 
28
- export function parseChatKey(key) {
29
- const parts = String(key || "").split(":")
30
- if (parts.length < 2) return null
31
- const platform = parts[0]
32
- const chatId = parts[1]
33
- const threadId = parts.length >= 3 ? normalizeThreadId(parts[2]) : 0
34
- const out = { platform, chatId, threadId }
35
- if (parts.length >= 5 && parts[3] === "u") {
36
- const userId = Number(parts[4])
37
- if (Number.isFinite(userId)) out.userId = userId
38
- }
39
- return out
40
- }
41
-
42
28
  /** Extra Telegram API fields for forum topic replies. */
43
29
  export function telegramThreadParams(threadId) {
44
30
  const tid = normalizeThreadId(threadId)
package/lib/tts.js CHANGED
@@ -57,16 +57,6 @@ export function isOggOpusMime(mime) {
57
57
  return m.includes('ogg') || m.includes('opus')
58
58
  }
59
59
 
60
- export function voiceReplyFile(spoken) {
61
- const mime = spoken?.mime || 'audio/mpeg'
62
- return {
63
- bytes: spoken.audio,
64
- mime,
65
- kind: 'voice',
66
- name: voiceFileNameForMime(mime),
67
- }
68
- }
69
-
70
60
  function runFfmpeg(args) {
71
61
  return new Promise((resolve, reject) => {
72
62
  const child = spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] })
package/lib/updater.js ADDED
@@ -0,0 +1,304 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { spawn } from 'node:child_process'
4
+ import { homedir } from 'node:os'
5
+ import { basename, dirname, isAbsolute, resolve } from 'node:path'
6
+ import { fileURLToPath } from 'node:url'
7
+ import { isTrustedSettingsRequest } from './http.js'
8
+
9
+ const UPDATE_HEADER = 'x-dsh-plugin-update'
10
+ const UPDATE_TIMEOUT_MS = 180_000
11
+ const VERSION_CACHE_MS = 300_000
12
+
13
+ let latestCache = null
14
+
15
+ function header(request, name) {
16
+ const value = request?.headers?.[name.toLowerCase()]
17
+ if (Array.isArray(value)) return value[0]
18
+ return typeof value === 'string' ? value : undefined
19
+ }
20
+
21
+ function isLoopback(value) {
22
+ const address = value?.toLowerCase().replace(/^\[|\]$/g, '')
23
+ return address === 'localhost' || address === 'localhost.' || address === '::1'
24
+ || address?.startsWith('127.') === true
25
+ || address?.startsWith('::ffff:127.') === true
26
+ }
27
+
28
+ export function isTrustedUpdateRequest(request) {
29
+ if (header(request, UPDATE_HEADER) !== '1') return false
30
+ if (!isLoopback(request?.socket?.remoteAddress)) return false
31
+ const site = header(request, 'sec-fetch-site')
32
+ if (site !== undefined && site !== 'same-origin') return false
33
+ const origin = header(request, 'origin')
34
+ const host = header(request, 'host')
35
+ if (origin === undefined || host === undefined) return false
36
+ try {
37
+ const url = new URL(origin)
38
+ return (url.protocol === 'http:' || url.protocol === 'https:')
39
+ && isLoopback(url.hostname) && url.host === host
40
+ } catch {
41
+ return false
42
+ }
43
+ }
44
+
45
+ function validProfileName(value) {
46
+ return typeof value === 'string' && value !== '' && value !== '.' && value !== '..'
47
+ && !value.includes('/') && !value.includes('\\') && !/[\0-\x1f\x7f]/.test(value)
48
+ }
49
+
50
+ function profileNameFromArgv(argv) {
51
+ for (let i = 0; i < argv.length; i++) {
52
+ const item = argv[i]
53
+ if (item === '--profile' || item === '-p') {
54
+ const next = argv[i + 1]
55
+ return validProfileName(next) ? next : undefined
56
+ }
57
+ if (typeof item === 'string' && item.startsWith('--profile=')) {
58
+ const value = item.slice('--profile='.length)
59
+ return validProfileName(value) ? value : undefined
60
+ }
61
+ }
62
+ return undefined
63
+ }
64
+
65
+ export function findDshCliEntry() {
66
+ const value = process.argv[1]
67
+ if (value === undefined || value === '') return undefined
68
+ const entry = value.startsWith('file:') ? fileURLToPath(value) : resolve(process.cwd(), value)
69
+ if (!existsSync(entry)) return undefined
70
+ for (let directory = dirname(entry); ; directory = dirname(directory)) {
71
+ const manifestPath = resolve(directory, 'package.json')
72
+ if (existsSync(manifestPath)) {
73
+ try {
74
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
75
+ const bin = typeof manifest.bin === 'string'
76
+ ? manifest.bin
77
+ : typeof manifest.bin === 'object' && manifest.bin !== null
78
+ ? manifest.bin.dsh
79
+ : undefined
80
+ if (manifest.name === '@deepseek-ai/dsh' && typeof bin === 'string'
81
+ && !isAbsolute(bin) && resolve(directory, bin) === resolve(entry)) return entry
82
+ } catch {
83
+ // Continue searching parent directories
84
+ }
85
+ }
86
+ const parent = dirname(directory)
87
+ if (parent === directory) return undefined
88
+ }
89
+ }
90
+
91
+ export function runtime() {
92
+ const profileDir = resolve(process.env.DSH_PROFILE_DIR
93
+ ?? resolve(homedir(), '.dsh', 'profiles', 'web'))
94
+ const selected = profileNameFromArgv(process.argv)
95
+ const profileName = validProfileName(selected)
96
+ ? selected
97
+ : validProfileName(basename(profileDir)) ? basename(profileDir) : 'web'
98
+ const cliEntry = findDshCliEntry()
99
+ return cliEntry === undefined ? { profileName, profileDir } : { profileName, profileDir, cliEntry }
100
+ }
101
+
102
+ export function parseSemver(value) {
103
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(String(value || '').trim())
104
+ if (match === null) return undefined
105
+ return {
106
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
107
+ prerelease: match[4]?.split('.') ?? [],
108
+ }
109
+ }
110
+
111
+ function comparePrerelease(left, right) {
112
+ if (left.length === 0 || right.length === 0) return left.length === right.length ? 0 : left.length === 0 ? 1 : -1
113
+ const length = Math.max(left.length, right.length)
114
+ for (let index = 0; index < length; index += 1) {
115
+ const a = left[index]
116
+ const b = right[index]
117
+ if (a === undefined || b === undefined) return a === b ? 0 : a === undefined ? -1 : 1
118
+ if (a === b) continue
119
+ const aNumeric = /^\d+$/.test(a)
120
+ const bNumeric = /^\d+$/.test(b)
121
+ if (aNumeric && bNumeric) {
122
+ const aNumber = BigInt(a)
123
+ const bNumber = BigInt(b)
124
+ if (aNumber !== bNumber) return aNumber > bNumber ? 1 : -1
125
+ continue
126
+ }
127
+ if (aNumeric !== bNumeric) return aNumeric ? -1 : 1
128
+ return a > b ? 1 : -1
129
+ }
130
+ return 0
131
+ }
132
+
133
+ export function isNewerVersion(currentValue, candidateValue) {
134
+ const current = parseSemver(currentValue)
135
+ const candidate = parseSemver(candidateValue)
136
+ if (current === undefined || candidate === undefined) return false
137
+ for (let index = 0; index < 3; index += 1) {
138
+ if (candidate.core[index] !== current.core[index]) return candidate.core[index] > current.core[index]
139
+ }
140
+ return comparePrerelease(candidate.prerelease, current.prerelease) > 0
141
+ }
142
+
143
+ export async function fetchLatestVersion(packageName, registry = 'https://registry.npmjs.org') {
144
+ if (latestCache?.packageName === packageName && latestCache.registry === registry && Date.now() < latestCache.expiresAt) {
145
+ return latestCache.version
146
+ }
147
+ try {
148
+ const cleanReg = registry.replace(/\/$/, '')
149
+ const response = await fetch(`${cleanReg}/${encodeURIComponent(packageName)}/latest`, {
150
+ headers: { accept: 'application/json' },
151
+ signal: AbortSignal.timeout(8000),
152
+ })
153
+ if (!response.ok) return undefined
154
+ const value = await response.json()
155
+ if (typeof value.version !== 'string' || value.version === '') return undefined
156
+ latestCache = { packageName, registry, version: value.version, expiresAt: Date.now() + VERSION_CACHE_MS }
157
+ return value.version
158
+ } catch {
159
+ return undefined
160
+ }
161
+ }
162
+
163
+ export async function currentVersion(manifestPath) {
164
+ const raw = await readFile(manifestPath, 'utf8')
165
+ const value = JSON.parse(raw)
166
+ if (typeof value.version !== 'string' || value.version === '') throw new Error('Cannot read current plugin version.')
167
+ return value.version
168
+ }
169
+
170
+ export async function getUpdateStatus(options, target = runtime()) {
171
+ const current = await currentVersion(options.manifestPath)
172
+ const latest = await fetchLatestVersion(options.packageName, options.registry ?? 'https://registry.npmjs.org')
173
+ return {
174
+ packageName: options.packageName,
175
+ currentVersion: current,
176
+ ...(latest === undefined ? {} : { latestVersion: latest }),
177
+ latestCheckFailed: latest === undefined,
178
+ updateAvailable: latest !== undefined && isNewerVersion(current, latest),
179
+ profileName: target.profileName,
180
+ canAutoUpdate: target.cliEntry !== undefined,
181
+ }
182
+ }
183
+
184
+ export async function installExact(target, packageSpec, options = {}) {
185
+ if (target.cliEntry === undefined) throw new Error('Automatic update CLI entry is unavailable in this runtime.')
186
+ return new Promise((resolvePromise, reject) => {
187
+ const args = [
188
+ target.cliEntry, 'plugin', '--profile', target.profileName, 'add',
189
+ '--config.minimumReleaseAge=0', packageSpec,
190
+ `--registry=${options.registry ?? 'https://registry.npmjs.org/'}`,
191
+ ]
192
+ const child = spawn(process.execPath, args, {
193
+ cwd: target.profileDir,
194
+ windowsHide: true,
195
+ stdio: ['ignore', 'pipe', 'pipe'],
196
+ env: { ...process.env, NO_COLOR: '1' },
197
+ })
198
+ let detail = ''
199
+ child.stdout?.on('data', (chunk) => { detail = (detail + String(chunk)).slice(-4000) })
200
+ child.stderr?.on('data', (chunk) => { detail = (detail + String(chunk)).slice(-4000) })
201
+ const timer = setTimeout(() => {
202
+ child.kill()
203
+ reject(new Error('Update timed out; use the standard DSH update flow.'))
204
+ }, UPDATE_TIMEOUT_MS)
205
+ child.once('error', (error) => { clearTimeout(timer); reject(error) })
206
+ child.once('exit', (code) => {
207
+ clearTimeout(timer)
208
+ if (code === 0) resolvePromise({ ok: true })
209
+ else reject(new Error(detail.trim() || `Update exited with code ${String(code)}.`))
210
+ })
211
+ })
212
+ }
213
+
214
+ let isGlobalUpdating = false
215
+
216
+ export async function runDirectUpdate(options, target = runtime()) {
217
+ if (isGlobalUpdating) {
218
+ throw new Error('Update is already in progress.')
219
+ }
220
+ isGlobalUpdating = true
221
+ try {
222
+ const status = await getUpdateStatus(options, target)
223
+ if (status.latestVersion === undefined) {
224
+ throw new Error('Latest version information is temporarily unavailable.')
225
+ }
226
+ if (!status.updateAvailable) {
227
+ return { ...status, updated: false, message: 'Already up to date.' }
228
+ }
229
+ await installExact(target, `${options.packageName}@${status.latestVersion}`, options)
230
+ return {
231
+ ...status,
232
+ updated: true,
233
+ updatedVersion: status.latestVersion,
234
+ restartRequired: true,
235
+ }
236
+ } finally {
237
+ isGlobalUpdating = false
238
+ }
239
+ }
240
+
241
+ export function registerPluginUpdater(ctx, options) {
242
+ const getWebServer = () => ctx.get?.('webServer') || ctx.webServer
243
+ return getWebServer().register({
244
+ kind: 'exact',
245
+ path: options.endpoint || '/dsh-messenger-gateway/update',
246
+ handler: async (request, response) => {
247
+ try {
248
+ const target = runtime()
249
+ if (request.method === 'GET' || request.method === 'HEAD') {
250
+ const payload = await getUpdateStatus(options, target)
251
+ response.writeHead(200, {
252
+ 'content-type': 'application/json; charset=utf-8',
253
+ 'cache-control': 'no-store',
254
+ })
255
+ response.end(request.method === 'HEAD' ? undefined : JSON.stringify(payload))
256
+ return
257
+ }
258
+ if (request.method !== 'POST') {
259
+ response.writeHead(405, { allow: 'GET, HEAD, POST' })
260
+ response.end()
261
+ return
262
+ }
263
+ if (!isTrustedUpdateRequest(request)) {
264
+ response.writeHead(403, { 'content-type': 'application/json; charset=utf-8' })
265
+ response.end(JSON.stringify({ ok: false, error: 'Rejected untrusted or cross-origin update request.' }))
266
+ return
267
+ }
268
+ if (isGlobalUpdating) {
269
+ response.writeHead(409, { 'content-type': 'application/json; charset=utf-8' })
270
+ response.end(JSON.stringify({ ok: false, error: 'This plugin is already updating.' }))
271
+ return
272
+ }
273
+ isGlobalUpdating = true
274
+ try {
275
+ const before = await getUpdateStatus(options, target)
276
+ if (before.latestVersion === undefined) {
277
+ response.writeHead(503, { 'content-type': 'application/json; charset=utf-8' })
278
+ response.end(JSON.stringify({ ok: false, error: 'The latest version is temporarily unavailable.' }))
279
+ return
280
+ }
281
+ if (!before.updateAvailable) {
282
+ response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })
283
+ response.end(JSON.stringify({ ...before, updated: false }))
284
+ return
285
+ }
286
+ await installExact(target, `${options.packageName}@${before.latestVersion}`, options)
287
+ response.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })
288
+ response.end(JSON.stringify({
289
+ ...before,
290
+ updated: true,
291
+ updatedVersion: before.latestVersion,
292
+ restartRequired: true,
293
+ }))
294
+ } finally {
295
+ isGlobalUpdating = false
296
+ }
297
+ } catch (error) {
298
+ ctx.logger?.warn?.(`plugin updater error: ${error?.message || String(error)}`)
299
+ response.writeHead(500, { 'content-type': 'application/json; charset=utf-8' })
300
+ response.end(JSON.stringify({ ok: false, error: error?.message || 'Plugin update failed.' }))
301
+ }
302
+ },
303
+ })
304
+ }
@@ -10,25 +10,29 @@ function writeJsonAtomicSync(filePath, data) {
10
10
  writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf8")
11
11
  renameSync(tmpPath, filePath)
12
12
  } catch (err) {
13
- try { unlinkSync(tmpPath) } catch {}
13
+ try { unlinkSync(tmpPath) } catch { /* safe best-effort temp file cleanup */ }
14
14
  throw err
15
15
  }
16
16
  }
17
17
 
18
- export function createVoicePrefs(filePath) {
18
+ export function createVoicePrefs(filePath, { logger } = {}) {
19
19
  /** @type {Record<string, boolean>} */
20
20
  let state = {}
21
21
  if (filePath && existsSync(filePath)) {
22
22
  try {
23
23
  const raw = JSON.parse(readFileSync(filePath, "utf8"))
24
24
  if (raw && typeof raw === "object") state = raw
25
- } catch {}
25
+ } catch (err) {
26
+ logger?.warn?.('[dsh-messenger-gateway] voice prefs read error:', err?.message || err)
27
+ }
26
28
  }
27
29
  const persist = () => {
28
30
  if (!filePath) return
29
31
  try {
30
32
  writeJsonAtomicSync(filePath, state)
31
- } catch {}
33
+ } catch (err) {
34
+ logger?.warn?.('[dsh-messenger-gateway] voice prefs persist error:', err?.message || err)
35
+ }
32
36
  }
33
37
  const key = (userId) => String(Number(userId) || userId || "")
34
38
  return {
package/package.json CHANGED
@@ -1,65 +1,69 @@
1
- {
2
- "name": "@goodandready/dsh-messenger-gateway",
3
- "version": "0.3.21",
4
- "description": "Telegram messenger bridge for DeepSeek Harness: sessions, steer, homes, inline asks, notify bridge, and optional TTS voice notes.",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "./lib/index.js",
8
- "exports": {
9
- ".": "./lib/index.js",
10
- "./client": "./lib/client.js",
11
- "./package.json": "./package.json",
12
- "./cordis.patch.yml": "./cordis.patch.yml"
13
- },
14
- "files": [
15
- "lib/",
16
- "cordis.patch.yml",
17
- "README.md",
18
- "LICENSE"
19
- ],
20
- "keywords": [
21
- "dsh",
22
- "dsh-plugin",
23
- "telegram",
24
- "messenger",
25
- "deepseek-harness",
26
- "bot"
27
- ],
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/GooDAnDReaDY/dsh-messenger-gateway.git"
31
- },
32
- "homepage": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway#readme",
33
- "scripts": {
34
- "test": "node --test test/*.test.mjs",
35
- "test:load": "node scripts/verify-dsh-load.mjs",
36
- "test:all": "npm test && npm run test:load",
37
- "smoke:http": "node scripts/smoke-http.mjs"
38
- },
39
- "dsh": {
40
- "bundle": {
41
- "patch": "./cordis.patch.yml"
42
- },
43
- "client": {
44
- "platform": "web",
45
- "inject": []
46
- }
47
- },
48
- "peerDependencies": {
49
- "@deepseek-ai/cordis": "^4.0.2",
50
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
51
- "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.2",
52
- "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
53
- "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
54
- "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
55
- "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
56
- "@deepseek-ai/schemastery": "^3.18.2",
57
- "@deepseek-ai/dsh-tools": "^0.1.2-alpha.2"
58
- },
59
- "publishConfig": {
60
- "access": "public"
61
- },
62
- "bugs": {
63
- "url": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway/issues"
64
- }
65
- }
1
+ {
2
+ "name": "@goodandready/dsh-messenger-gateway",
3
+ "version": "0.4.0",
4
+ "description": "Telegram messenger bridge for DeepSeek Harness: sessions, steer, homes, inline asks, notify bridge, and optional TTS voice notes.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./lib/index.js",
8
+ "exports": {
9
+ ".": "./lib/index.js",
10
+ "./client": "./lib/client.js",
11
+ "./package.json": "./package.json",
12
+ "./cordis.patch.yml": "./cordis.patch.yml"
13
+ },
14
+ "files": [
15
+ "lib/",
16
+ "cordis.patch.yml",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "keywords": [
21
+ "dsh",
22
+ "dsh-plugin",
23
+ "telegram",
24
+ "messenger",
25
+ "deepseek-harness",
26
+ "bot"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/GooDAnDReaDY/dsh-messenger-gateway.git"
31
+ },
32
+ "homepage": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway#readme",
33
+ "scripts": {
34
+ "test": "node --test test/*.test.mjs",
35
+ "test:load": "node scripts/verify-dsh-load.mjs",
36
+ "test:all": "npm test && npm run test:load",
37
+ "smoke:http": "node scripts/smoke-http.mjs"
38
+ },
39
+ "dsh": {
40
+ "bundle": {
41
+ "patch": "./cordis.patch.yml"
42
+ },
43
+ "client": {
44
+ "platform": "web",
45
+ "inject": [
46
+ "@deepseek-ai/dsh-client-locale",
47
+ "@deepseek-ai/dsh-client-ui-slots",
48
+ "@deepseek-ai/dsh-client-ui-settings"
49
+ ]
50
+ }
51
+ },
52
+ "peerDependencies": {
53
+ "@deepseek-ai/cordis": "^4.0.2",
54
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
55
+ "@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.2",
56
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
57
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.2",
58
+ "@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
59
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
60
+ "@deepseek-ai/schemastery": "^3.18.2",
61
+ "@deepseek-ai/dsh-tools": "^0.1.2-alpha.2"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/GooDAnDReaDY/dsh-messenger-gateway/issues"
68
+ }
69
+ }