dsh-remote-plugin 0.6.0 → 0.6.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.
Binary file
package/gateway.cjs CHANGED
@@ -1622,6 +1622,10 @@ function proxyApi(req, res, url) {
1622
1622
  headers[k] = v
1623
1623
  }
1624
1624
  headers.host = UPSTREAM.host
1625
+ // /remote/* 由 DSH 插件端点处理;插件侧用网关自身 token 鉴权。
1626
+ if (url.pathname.startsWith('/remote/')) {
1627
+ headers.authorization = 'Bearer ' + TOKEN
1628
+ }
1625
1629
 
1626
1630
  const upstreamReq = http.request({
1627
1631
  hostname: UPSTREAM.hostname,
@@ -1673,6 +1677,7 @@ const server = http.createServer((req, res) => {
1673
1677
  if (url.pathname.startsWith('/admin/api')) return serveAdminApi(req, res, url)
1674
1678
  if (url.pathname.startsWith('/stats')) return serveStats(req, res, url)
1675
1679
  if (url.pathname === '/api/events.poll') return serveEventPoll(req, res, url)
1680
+ if (url.pathname.startsWith('/remote/')) return proxyApi(req, res, url)
1676
1681
  if (url.pathname.startsWith('/api/')) return proxyApi(req, res, url)
1677
1682
  if (url.pathname === '/health') return serveHealth(res)
1678
1683
  touchDevice(req)
package/index.mjs CHANGED
@@ -13,7 +13,7 @@ import { dirname, extname, normalize, resolve } from 'node:path'
13
13
  import { fileURLToPath } from 'node:url'
14
14
 
15
15
  export const name = 'dsh-remote'
16
- export const inject = ['webServer']
16
+ export const inject = ['webServer', 'commands', 'agents']
17
17
 
18
18
  const MOUNT = '/remote'
19
19
  const PUBLIC_DIR = fileURLToPath(new URL('./public/', import.meta.url))
@@ -284,6 +284,25 @@ function statsSend(session, event) {
284
284
  statsQueues.set(session.id, next)
285
285
  }
286
286
 
287
+ /** 从 sessionId 解析 DSH Agent:优先取已发布 live agent,否则走 resume。返回 { agent, resolvePath } 便于定位。 */
288
+ async function resolveAgent(ctx, sessionId) {
289
+ if (!ctx.agents) return { agent: null, resolvePath: 'no-agents-service' }
290
+ let live
291
+ try {
292
+ live = ctx.agents.get(sessionId)
293
+ } catch (e) {
294
+ live = undefined
295
+ }
296
+ if (live) return { agent: live, resolvePath: 'live' }
297
+ try {
298
+ const handle = await ctx.agents.resume({ resumeSessionId: sessionId })
299
+ if (!handle || !handle.agent) return { agent: null, resolvePath: 'resume-empty-handle' }
300
+ return { agent: handle.agent, resolvePath: 'resume' }
301
+ } catch (e) {
302
+ return { agent: null, resolvePath: 'resume-error: ' + (e?.message || String(e)) }
303
+ }
304
+ }
305
+
287
306
  async function resolveFile(pathname) {
288
307
  let abs = targetPath(pathname)
289
308
  if (abs === null) return null
@@ -313,7 +332,7 @@ async function resolveFile(pathname) {
313
332
  }
314
333
  }
315
334
 
316
- async function serveStatic(req, res) {
335
+ async function serveStatic(req, res, ctx) {
317
336
  const pathname = new URL(req.url ?? '/', 'http://x').pathname
318
337
 
319
338
  // 无尾斜杠的入口重定向到带斜杠版本:
@@ -417,6 +436,52 @@ async function serveStatic(req, res) {
417
436
  return
418
437
  }
419
438
 
439
+ // 斜杠命令桥接:客户端 → 网关 → 插件端点 → ctx.commands.execute
440
+ if (pathname === `${MOUNT}/api/command`) {
441
+ if (req.method !== 'POST') {
442
+ res.writeHead(405, { allow: 'POST' })
443
+ res.end()
444
+ return
445
+ }
446
+ const auth = req.headers.authorization || ''
447
+ const expected = gatewayToken()
448
+ if (!expected || auth !== `Bearer ${expected}`) {
449
+ sendJson(res, 401, { ok: false, message: 'unauthorized' })
450
+ return
451
+ }
452
+ let body
453
+ try {
454
+ body = JSON.parse((await readBody(req, 8192)) || '{}')
455
+ } catch {
456
+ sendJson(res, 400, { ok: false, message: 'invalid json' })
457
+ return
458
+ }
459
+ const { sessionId, line } = body || {}
460
+ if (!sessionId || typeof line !== 'string') {
461
+ sendJson(res, 400, { ok: false, message: 'sessionId and line required' })
462
+ return
463
+ }
464
+ try {
465
+ const { agent, resolvePath } = await resolveAgent(ctx, sessionId)
466
+ if (!agent) {
467
+ sendJson(res, 200, { ok: false, message: 'agent not found', debug: { resolvePath, commandNames: [] } })
468
+ return
469
+ }
470
+ let commandNames = []
471
+ try {
472
+ commandNames = (await ctx.commands.list(agent)).map(c => c.name)
473
+ } catch (e) {
474
+ commandNames = ['list-error: ' + (e?.message || String(e))]
475
+ }
476
+ const signal = AbortSignal.timeout(30000)
477
+ const result = await ctx.commands.execute(agent, line, signal)
478
+ sendJson(res, 200, { ok: true, executed: result !== undefined, debug: { resolvePath, commandNames } })
479
+ } catch (e) {
480
+ sendJson(res, 200, { ok: false, message: e?.message || String(e) })
481
+ }
482
+ return
483
+ }
484
+
420
485
  if (req.method !== 'GET' && req.method !== 'HEAD') {
421
486
  res.writeHead(405, { allow: 'GET, HEAD' })
422
487
  res.end()
@@ -446,7 +511,7 @@ export function apply(ctx) {
446
511
  ctx.effect(() => ctx.webServer.register({
447
512
  kind: 'prefix',
448
513
  path: MOUNT,
449
- handler: serveStatic,
514
+ handler: (req, res) => serveStatic(req, res, ctx),
450
515
  }), 'dsh-remote: /remote route')
451
516
  // 实时统计: 监听 DSH 会话事件流, 把带 usage 的 assistant/message 投递到网关聚合
452
517
  ctx.on('session/event', (session, event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote-plugin",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "DSH Remote 官方 bundle 插件:DSH 左侧原生边栏入口 + 右侧抽屉内嵌管理控制台;内置网关随 DSH 自动启停(systemd 独立单元),抽屉直显令牌与设备监控;网关提供 /fs/* 文件传输端点(列表/断点下载/上传),配合 Android App 远程操控会话/审批/提问/goal 与文件互传(多服务器测速切换、聊天记录离线缓存)。",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",
package/public/admin.html CHANGED
@@ -184,6 +184,7 @@
184
184
  <svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8a8.01 8.01 0 0 0 5.47 7.59c.4.08.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg>
185
185
  <span class="gh-label" data-i18n="repo">仓库</span>
186
186
  </a>
187
+ <button id="btn-donate" class="mini-btn tb-btn" data-i18n-title="donateTitle">🎁</button>
187
188
  <div class="fb-wrap">
188
189
  <button id="btn-feedback" class="mini-btn fb-btn tb-btn" data-i18n-title="feedbackTitle" data-i18n-aria="feedbackTitle" aria-haspopup="menu" aria-expanded="false">
189
190
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a8 8 0 0 1-8 8H5l-1.5 2L4 20a8 8 0 0 1-1-4 8 8 0 0 1 18-4Z"/></svg>
@@ -271,6 +272,20 @@
271
272
  </div>
272
273
  </div>
273
274
 
275
+ <!-- 赞赏支持模态 -->
276
+ <div id="modal-donate" class="modal hidden">
277
+ <div class="modal-card">
278
+ <div class="modal-title" data-i18n="donateTitle">赞赏支持</div>
279
+ <div class="modal-body donate-body">
280
+ <img src="donate.png" alt="" class="donate-img" style="display:block;margin:0 auto;max-width:100%;max-height:60vh;border-radius:8px">
281
+ <p class="donate-thanks" data-i18n="donateThanks" style="text-align:center;margin-top:12px">感谢你的支持 ☕</p>
282
+ </div>
283
+ <div class="modal-actions">
284
+ <button id="donate-close" class="btn subtle" data-i18n="theme.close">关闭</button>
285
+ </div>
286
+ </div>
287
+ </div>
288
+
274
289
  <div id="toast" class="toast hidden"></div>
275
290
  <script>
276
291
  window.ADMIN_STR = {
@@ -282,6 +297,7 @@
282
297
  'theme.default': '默认深空', 'theme.dark': '落日', 'theme.light': '易北爱乐厅', 'theme.neutral': '草原孤塔',
283
298
  'theme.panelTitle': '选择配色', 'theme.close': '关闭',
284
299
  'repo': '仓库',
300
+ 'donateTitle': '赞赏支持', 'donateThanks': '感谢你的支持 ☕',
285
301
  'feedbackTitle': '反馈渠道',
286
302
  'feedback.githubDesc': '反馈 bug / 提建议', 'feedback.giteeDesc': '国内镜像,无需代理', 'feedback.biliDesc': 'UP 动态页交流',
287
303
  'unauth': '未认证',
@@ -356,6 +372,7 @@
356
372
  'theme.default': 'Default', 'theme.dark': 'Sunset', 'theme.light': 'Elbphilharmonie', 'theme.neutral': 'Prairie Tower',
357
373
  'theme.panelTitle': 'Choose theme', 'theme.close': 'Close',
358
374
  'repo': 'Repo',
375
+ 'donateTitle': 'Support', 'donateThanks': 'Thanks for your support ☕',
359
376
  'feedbackTitle': 'Feedback',
360
377
  'feedback.githubDesc': 'Report bugs · suggest features', 'feedback.giteeDesc': 'Mirror in China, no proxy needed', 'feedback.biliDesc': 'Chat on the UP\'s Bilibili page',
361
378
  'unauth': 'Not connected',
package/public/admin.js CHANGED
@@ -482,6 +482,11 @@ function openThemePanel() {
482
482
  $('modal-theme').classList.remove('hidden')
483
483
  }
484
484
 
485
+ function openDonateModal() {
486
+ const m = $('modal-donate')
487
+ if (m) m.classList.remove('hidden')
488
+ }
489
+
485
490
  $('btn-lang').addEventListener('click', () => {
486
491
  I18N.setLang(I18N.lang === 'zh' ? 'en' : 'zh')
487
492
  renderLangBtn()
@@ -492,6 +497,10 @@ $('btn-lang').addEventListener('click', () => {
492
497
 
493
498
  $('btn-theme').addEventListener('click', openThemePanel)
494
499
  $('theme-close').addEventListener('click', () => $('modal-theme').classList.add('hidden'))
500
+ // 赞赏支持
501
+ $('btn-donate').addEventListener('click', openDonateModal)
502
+ $('donate-close').addEventListener('click', () => $('modal-donate').classList.add('hidden'))
503
+ $('modal-donate').addEventListener('click', (e) => { if (e.target === $('modal-donate')) $('modal-donate').classList.add('hidden') })
495
504
  // 反馈
496
505
  $('btn-feedback').addEventListener('click', (e) => { e.stopPropagation(); toggleFeedbackMenu() })
497
506
  $('fb-menu').addEventListener('click', (e) => {