@goodandready/dsh-messenger-gateway 0.3.11 → 0.3.13

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.
Files changed (2) hide show
  1. package/lib/client.js +139 -52
  2. package/package.json +1 -3
package/lib/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // dsh-messenger-gateway — browser (client) half.
2
2
  // Settings card on Plugins → Plugin settings tab (settings.plugin.item).
3
- // Fallback to sidebar settings.section when the slot is unavailable.
3
+ // Config managed via reactive ctx.settingsScope snapshot.
4
4
 
5
5
  window.__ModuleLoader__.load({
6
6
  id: '@goodandready/dsh-messenger-gateway',
@@ -52,6 +52,7 @@ const css =
52
52
  description: 'Telegram bot: text, voice, photos and documents.',
53
53
  loading: 'Loading…',
54
54
  save: 'Save',
55
+ saved: 'Saved',
55
56
  saving: 'Saving…',
56
57
  showAdvanced: 'Advanced settings',
57
58
  hideAdvanced: 'Hide advanced',
@@ -125,6 +126,7 @@ const css =
125
126
  description: 'Telegram-бот: текст, голос, фото и документы.',
126
127
  loading: 'Загрузка…',
127
128
  save: 'Сохранить',
129
+ saved: 'Сохранено',
128
130
  saving: 'Сохранение…',
129
131
  showAdvanced: 'Расширенные настройки',
130
132
  hideAdvanced: 'Скрыть расширенные',
@@ -227,16 +229,104 @@ const css =
227
229
  )
228
230
  }
229
231
 
230
- function MessengerSettingsForm({ t }) {
231
- const [cfg, setCfg] = React.useState(null)
232
+ const SETTINGS_KEYS = ['enabled', 'telegram', 'agent', 'media', 'tts']
233
+
234
+ function draftFromStored(s) {
235
+ const src = (s && typeof s === 'object') ? s : {}
236
+ const t = src.telegram || {}
237
+ const a = src.agent || {}
238
+ const m = src.media || {}
239
+ const tts = src.tts || {}
240
+ return {
241
+ enabled: src.enabled !== false,
242
+ telegram: {
243
+ enabled: t.enabled === true,
244
+ allowedUserIds: Array.isArray(t.allowedUserIds) ? t.allowedUserIds : [],
245
+ pollTimeoutSeconds: Number(t.pollTimeoutSeconds) || 50,
246
+ pollIntervalMs: Number(t.pollIntervalMs) || 500,
247
+ commands: Array.isArray(t.commands) ? t.commands : [],
248
+ textFormat: t.textFormat === 'plain' ? 'plain' : 'html',
249
+ homeChatId: t.homeChatId != null ? t.homeChatId : '',
250
+ homeThreadId: Number(t.homeThreadId) || 0,
251
+ homes: Array.isArray(t.homes) ? t.homes : [],
252
+ pairingEnabled: t.pairingEnabled !== false,
253
+ streaming: t.streaming === true,
254
+ streamEditIntervalMs: Number(t.streamEditIntervalMs) || 1200,
255
+ progressEnabled: t.progressEnabled !== false,
256
+ approvalsEnabled: t.approvalsEnabled !== false,
257
+ groupsEnabled: t.groupsEnabled !== false,
258
+ groupRequireMention: t.groupRequireMention !== false,
259
+ reactionsEnabled: t.reactionsEnabled !== false,
260
+ statusIndicator: t.statusIndicator === true,
261
+ statusOnline: t.statusOnline || 'Online',
262
+ statusOffline: t.statusOffline || 'Offline',
263
+ transport: t.transport === 'webhook' ? 'webhook' : 'poll',
264
+ webhookUrl: t.webhookUrl || '',
265
+ webhookPath: t.webhookPath || '/dsh-messenger-gateway/telegram/webhook',
266
+ voiceMode: t.voiceMode || 'mirror',
267
+ quickActions: t.quickActions === true,
268
+ artifactPreviews: t.artifactPreviews !== false,
269
+ notifyBridge: t.notifyBridge || { enabled: false, events: ['task_done', 'error'], home: 'default', excludeSessionPrefixes: ['msgw-'] },
270
+ alerts: t.alerts || { enabled: false, chatId: '', threadId: 0, home: '', events: ['error', 'pairing'] },
271
+ botToken: t.botToken || '',
272
+ webhookSecret: t.webhookSecret || '',
273
+ },
274
+ agent: {
275
+ provider: a.provider || '',
276
+ model: a.model || '',
277
+ instructionPrefix: a.instructionPrefix || '',
278
+ maxMessageLength: Number(a.maxMessageLength) || 4000,
279
+ turnTimeoutMs: Number(a.turnTimeoutMs) || 600000,
280
+ idleTimeoutMs: Number(a.idleTimeoutMs) || 3600000,
281
+ photoOnlyMode: a.photoOnlyMode || 'prompt',
282
+ sessionScope: a.sessionScope || 'user',
283
+ },
284
+ media: {
285
+ maxDocBytes: Number(m.maxDocBytes) || 20971520,
286
+ maxImageBytes: Number(m.maxImageBytes) || 20971520,
287
+ maxTextInjectBytes: Number(m.maxTextInjectBytes) || 102400,
288
+ },
289
+ tts: {
290
+ enabled: tts.enabled === true,
291
+ maxChars: Number(tts.maxChars) || 4000,
292
+ voiceSummary: tts.voiceSummary === true,
293
+ },
294
+ }
295
+ }
296
+
297
+ function MessengerSettingsForm({ t, ctx }) {
232
298
  const [token, setToken] = React.useState('')
233
299
  const [webhookSecret, setWebhookSecret] = React.useState('')
234
300
  const [allowText, setAllowText] = React.useState('')
235
301
  const [pending, setPending] = React.useState([])
236
302
  const [err, setErr] = React.useState('')
303
+ const [msg, setMsg] = React.useState('')
237
304
  const [busy, setBusy] = React.useState(false)
238
305
  const [showAdvanced, setShowAdvanced] = React.useState(false)
239
306
 
307
+ const scope = React.useMemo(
308
+ () => (ctx && ctx.settingsScope ? ctx.settingsScope.bind({ namespace: NS }) : undefined),
309
+ [ctx],
310
+ )
311
+
312
+ const snapshot = React.useSyncExternalStore(
313
+ React.useMemo(() => (cb) => (scope ? scope.subscribe(cb) : () => {}), [scope]),
314
+ React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'loading' }), [scope]),
315
+ React.useCallback(() => ({ status: 'loading' }), []),
316
+ )
317
+
318
+ const snapStatus = (snapshot && snapshot.status) || 'loading'
319
+ const stored = (snapshot && snapshot.value) || {}
320
+
321
+ const [cfg, setCfg] = React.useState(null)
322
+
323
+ React.useEffect(() => {
324
+ if (snapStatus === 'ready' && cfg === null) {
325
+ setCfg(draftFromStored(stored))
326
+ setAllowText(idsToText(stored?.telegram?.allowedUserIds))
327
+ }
328
+ }, [snapStatus, stored, cfg])
329
+
240
330
  const loadPairing = React.useCallback(async () => {
241
331
  try {
242
332
  const res = await fetch('/dsh-messenger-gateway/pairing', { credentials: 'same-origin' })
@@ -245,16 +335,9 @@ const css =
245
335
  } catch {}
246
336
  }, [])
247
337
 
248
- const load = React.useCallback(async () => {
249
- const res = await fetch('/dsh-messenger-gateway/config', { credentials: 'same-origin' })
250
- const data = await res.json()
251
- if (!res.ok || !data.ok) throw new Error(data.error || res.status)
252
- setCfg(data.config)
253
- setAllowText(idsToText(data.config?.telegram?.allowedUserIds))
254
- await loadPairing()
255
- }, [loadPairing])
256
-
257
- React.useEffect(() => { load().catch((e) => setErr(String(e.message || e))) }, [load])
338
+ React.useEffect(() => {
339
+ if (snapStatus === 'ready') loadPairing()
340
+ }, [snapStatus, loadPairing])
258
341
 
259
342
  const mergePatch = (base, patch) => ({
260
343
  ...base,
@@ -266,26 +349,47 @@ const css =
266
349
  })
267
350
 
268
351
  const save = async (patch = {}) => {
269
- setBusy(true); setErr('')
352
+ if (!scope || !cfg) return
353
+ setBusy(true); setErr(''); setMsg('')
270
354
  try {
271
355
  let next = mergePatch(cfg, patch)
272
356
  if (token.trim()) next.telegram = { ...next.telegram, botToken: token.trim() }
273
357
  if (webhookSecret.trim()) next.telegram = { ...next.telegram, webhookSecret: webhookSecret.trim() }
274
358
  next.telegram.allowedUserIds = textToIds(allowText)
275
- const res = await fetch('/dsh-messenger-gateway/config', {
276
- method: 'PUT', credentials: 'same-origin',
277
- headers: { 'Content-Type': 'application/json' },
278
- body: JSON.stringify({ config: next }),
279
- })
280
- const data = await res.json()
281
- if (!res.ok || !data.ok) throw new Error(data.error || res.status)
282
- setCfg(data.config); setToken(''); setWebhookSecret('')
283
- setAllowText(idsToText(data.config?.telegram?.allowedUserIds))
359
+
360
+ const broken = []
361
+ for (const key of SETTINGS_KEYS) {
362
+ if (next[key] !== undefined) {
363
+ try {
364
+ await scope.set(key, next[key])
365
+ } catch (e) {
366
+ broken.push(key + ': ' + (e && e.message || String(e)))
367
+ }
368
+ }
369
+ }
370
+ if (broken.length) {
371
+ setErr('Save failed — ' + broken.join('; '))
372
+ return
373
+ }
374
+ setCfg(next)
375
+ setToken('')
376
+ setWebhookSecret('')
377
+ setMsg(t('saved'))
378
+ setTimeout(() => setMsg(''), 3000)
284
379
  await loadPairing()
285
380
  } catch (e) { setErr(String(e.message || e)) } finally { setBusy(false) }
286
381
  }
287
382
 
288
- if (!cfg) return React.createElement('div', null, err || t('loading'))
383
+ if (snapStatus === 'loading') {
384
+ return React.createElement('div', { className: 'msgw-sub', style: { padding: '12px 0' } }, t('loading'))
385
+ }
386
+ if (snapStatus !== 'ready') {
387
+ return React.createElement('div', { className: 'msgw-err', style: { padding: '12px 0' } },
388
+ 'Settings unavailable (snapshot status: ' + snapStatus + '). Host namespace may be missing.')
389
+ }
390
+ if (!cfg) {
391
+ return React.createElement('div', { className: 'msgw-sub', style: { padding: '12px 0' } }, t('loading'))
392
+ }
289
393
 
290
394
  return React.createElement('div', { className: 'msgw-form' },
291
395
  React.createElement('div', null,
@@ -475,6 +579,7 @@ const css =
475
579
  ) : null,
476
580
 
477
581
  err ? React.createElement('div', { className: 'msgw-err' }, err) : null,
582
+ msg ? React.createElement('div', { style: { color: 'var(--dsw-alias-state-success-primary, #10b981)', fontSize: '13px', padding: '8px 0' } }, msg) : null,
478
583
  React.createElement('div', { className: 'msgw-foot' },
479
584
  React.createElement('button', { type: 'button', className: 'msgw-save', disabled: busy, onClick: () => save({}) }, busy ? t('saving') : t('save')),
480
585
  ),
@@ -498,7 +603,7 @@ const css =
498
603
  React.createElement('span', { className: 'msgw-chev' }, open ? '\u25B2' : '\u25BC'),
499
604
  ),
500
605
  open ? React.createElement('div', { className: 'msgw-body' },
501
- React.createElement(MessengerSettingsForm, { t }),
606
+ React.createElement(MessengerSettingsForm, { t, ctx: props.ctx }),
502
607
  ) : null,
503
608
  )
504
609
  }
@@ -507,36 +612,18 @@ const css =
507
612
  ctx.effect(() => ctx.locale.register(NS, { en, ru }), 'dsh-messenger-gateway: dictionaries')
508
613
  function useLocale() { return useActiveLocale(ctx) }
509
614
 
510
- const tryPluginItem = () => {
511
- try {
512
- ctx.slots.inject('settings.plugin.item', () =>
513
- ctx.slots.register({
514
- name: 'settings.plugin.item',
515
- key: NS,
516
- locale: NS,
517
- inject: () => ({ ctx }),
518
- }, (props) => React.createElement(PluginCard, { ...props, locale: useLocale() })),
519
- )
520
- return true
521
- } catch {
522
- return false
523
- }
524
- }
525
-
526
- if (!tryPluginItem()) {
527
- ctx.slots.inject('settings.section', () => ctx.slots.register({
528
- name: 'settings.section',
529
- id: NS,
530
- order: 36,
531
- label: () => makeT(useActiveLocale(ctx))('title'),
532
- }, (props) => React.createElement('div', { style: { padding: 16 } },
533
- React.createElement(MessengerSettingsForm, { t: makeT(useActiveLocale(ctx)) }),
534
- )))
535
- }
615
+ ctx.slots.inject('settings.plugin.item', () =>
616
+ ctx.slots.register({
617
+ name: 'settings.plugin.item',
618
+ key: NS,
619
+ locale: NS,
620
+ inject: () => ({ ctx }),
621
+ }, (props) => React.createElement(PluginCard, { ...props, ctx, locale: useLocale() })),
622
+ )
536
623
  }
537
624
 
538
625
  exports.apply = apply
539
- exports.inject = ['slots', 'locale']
626
+ exports.inject = ['slots', 'locale', 'settingsScope']
540
627
  return module.exports
541
628
  },
542
629
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-messenger-gateway",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Telegram messenger bridge for DeepSeek Harness: sessions, steer, homes, inline asks, notify bridge, and optional TTS voice notes.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -43,9 +43,7 @@
43
43
  "client": {
44
44
  "platform": "web",
45
45
  "inject": [
46
- "@deepseek-ai/dsh-client-runtime",
47
46
  "@deepseek-ai/dsh-client-locale",
48
- "@deepseek-ai/dsh-client-ui-slots",
49
47
  "@deepseek-ai/dsh-client-ui-settings"
50
48
  ]
51
49
  }