dsh-remote-plugin 0.7.0 → 0.7.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/apk/dsh-remote.apk +0 -0
- package/client.js +12 -2
- package/gateway.cjs +72 -8
- package/package.json +1 -1
- package/public/desktop/desktop.js +5 -3
- package/public/update.json +8 -8
- package/public/version.json +1 -1
package/apk/dsh-remote.apk
CHANGED
|
Binary file
|
package/client.js
CHANGED
|
@@ -20,7 +20,11 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
var DRAWER_STYLE = {
|
|
23
|
-
|
|
23
|
+
// Desktop's hidden title bar overlays the page (40px on Windows).
|
|
24
|
+
// Electron exposes its current height through the CSS environment value;
|
|
25
|
+
// keep a safe fallback for Desktop hosts without that value.
|
|
26
|
+
position: 'fixed', top: window.location && window.location.protocol === 'dsh-app:' ? 'calc(12px + env(titlebar-area-height, 40px))' : 12,
|
|
27
|
+
right: 12, bottom: 12, zIndex: 2147483000,
|
|
24
28
|
width: 'min(520px, calc(100vw - 24px))', background: '#0b0e1a',
|
|
25
29
|
border: '1px solid rgba(125, 207, 255, .22)', borderRadius: 24, overflow: 'hidden',
|
|
26
30
|
boxShadow: '-16px 18px 48px rgba(0,0,0,.46), 0 0 0 1px rgba(91,140,255,.08)',
|
|
@@ -141,7 +145,13 @@ window.__ModuleLoader__.load({
|
|
|
141
145
|
}, [])
|
|
142
146
|
return React.createElement('div', {
|
|
143
147
|
'aria-hidden': !open,
|
|
144
|
-
style: Object.assign({}, DRAWER_STYLE, {
|
|
148
|
+
style: Object.assign({}, DRAWER_STYLE, {
|
|
149
|
+
transform: open ? 'translateX(0)' : 'translateX(102%)',
|
|
150
|
+
boxShadow: open ? DRAWER_STYLE.boxShadow : 'none',
|
|
151
|
+
pointerEvents: open ? 'auto' : 'none',
|
|
152
|
+
visibility: open ? 'visible' : 'hidden',
|
|
153
|
+
transition: open ? 'transform .22s ease' : 'transform .22s ease, visibility 0s linear .22s',
|
|
154
|
+
}),
|
|
145
155
|
},
|
|
146
156
|
loaded.current ? React.createElement('iframe', {
|
|
147
157
|
src: '/remote/plugin.html',
|
package/gateway.cjs
CHANGED
|
@@ -1703,6 +1703,12 @@ async function translateModernRpc(method, payload, rpcId) {
|
|
|
1703
1703
|
if (!['create', 'rename', 'delete', 'insertBefore', 'insertSessionBefore', 'archiveSession'].includes(verb)) return legacyFallback('no generated RPC adapter')
|
|
1704
1704
|
endpoint = 'workspace/' + verb
|
|
1705
1705
|
args = { request: payload }
|
|
1706
|
+
if (verb === 'archiveSession') transform = value => {
|
|
1707
|
+
// The HTTP receipt precedes workspace/follow on another connection.
|
|
1708
|
+
// Publish the committed archive set before a client's immediate refresh.
|
|
1709
|
+
if (Array.isArray(value?.archivedSessionIds)) applyModernWorkspaceFrame({ type: 'archived', ...value })
|
|
1710
|
+
return value
|
|
1711
|
+
}
|
|
1706
1712
|
} else if (method.startsWith('goal.')) {
|
|
1707
1713
|
const verb = method.slice('goal.'.length)
|
|
1708
1714
|
if (!['create', 'edit', 'pause', 'resume', 'complete', 'clear'].includes(verb)) return legacyFallback('no generated RPC adapter')
|
|
@@ -1767,6 +1773,31 @@ async function translateModernRpc(method, payload, rpcId) {
|
|
|
1767
1773
|
return legacyEnvelope(rpcId, modernError(error.message, 'image-vision-failed'))
|
|
1768
1774
|
}
|
|
1769
1775
|
}
|
|
1776
|
+
if (method === 'subagent.list' && modernResponseNeedsLegacyFallback(response)) {
|
|
1777
|
+
// DSH 0.2 removed subagents/list; discovery is now a non-activating
|
|
1778
|
+
// read of the parent's durable catalog and each child's projections.
|
|
1779
|
+
const parent = await callUpstreamRemote('session/projections', { request: { sessionId: payload.parentSessionId } }, rpcId)
|
|
1780
|
+
if (modernResponseNeedsLegacyFallback(parent)) return legacyFallback('generated subagent catalog unavailable')
|
|
1781
|
+
if (!parent.body?.result?.ok) return parent.body || legacyEnvelope(rpcId, modernError('DSH subagent catalog read failed'))
|
|
1782
|
+
const catalog = parent.body.result.value?.values?.subagentCatalog
|
|
1783
|
+
if (!Array.isArray(catalog)) return legacyEnvelope(rpcId, modernError('DSH did not expose a subagent catalog projection', 'subagent/projections-unavailable'))
|
|
1784
|
+
await refreshModernSessions()
|
|
1785
|
+
const entries = await Promise.all(catalog.map(async child => {
|
|
1786
|
+
if (!['one-shot', 'continuable'].includes(child.mode)) return { kind: 'diagnostic', id: child.id, reason: 'unsupported' }
|
|
1787
|
+
try {
|
|
1788
|
+
const read = await callUpstreamRemote('session/projections', { request: { sessionId: child.id } })
|
|
1789
|
+
if (!read.body?.result?.ok || !read.body.result.value) return { kind: 'diagnostic', id: child.id, reason: 'unavailable' }
|
|
1790
|
+
const values = read.body.result.value.values
|
|
1791
|
+
const identity = values?.subagent
|
|
1792
|
+
if (!identity || identity.mode !== child.mode) return { kind: 'diagnostic', id: child.id, reason: 'corrupt' }
|
|
1793
|
+
return { kind: 'child', id: child.id, mode: identity.mode,
|
|
1794
|
+
...(identity.label === undefined ? {} : { label: identity.label }),
|
|
1795
|
+
activity: modernState.sessions.get(child.id)?.running ? 'running' : 'inactive',
|
|
1796
|
+
hasChildren: Array.isArray(values.subagentCatalog) && values.subagentCatalog.length > 0 }
|
|
1797
|
+
} catch { return { kind: 'diagnostic', id: child.id, reason: 'unavailable' } }
|
|
1798
|
+
}))
|
|
1799
|
+
return legacyEnvelope(rpcId, { ok: true, value: { entries, parentAvailable: !!modernState.sessions.get(payload.parentSessionId)?.agentAvailable } })
|
|
1800
|
+
}
|
|
1770
1801
|
if (modernResponseNeedsLegacyFallback(response)) return legacyFallback(`generated RPC ${endpoint} unavailable`)
|
|
1771
1802
|
if (!response.body?.result?.ok) {
|
|
1772
1803
|
recordCompatibility('generated-rpc-error', { method, status: response.status, detail: remoteFailureKind(response) })
|
|
@@ -1818,6 +1849,8 @@ function rememberCollectorReplay(kind, full, raw) {
|
|
|
1818
1849
|
let key = ''
|
|
1819
1850
|
if (payload.type === 'session/subscribed' && payload.sessionId) key = `session:${payload.sessionId}`
|
|
1820
1851
|
else if (payload.type === 'session/reasoning' && payload.sessionId) key = `reasoning:${payload.sessionId}`
|
|
1852
|
+
else if (payload.type === 'session/queue' && payload.sessionId) key = `queue:${payload.sessionId}`
|
|
1853
|
+
else if (payload.type === 'session/jobs' && payload.sessionId) key = `jobs:${payload.sessionId}`
|
|
1821
1854
|
else if (payload.type === 'approval/requested' && payload.approvalId) key = `approval:${payload.approvalId}`
|
|
1822
1855
|
else if (payload.type === 'question/requested' && full.rpcId) key = `question:${full.rpcId}`
|
|
1823
1856
|
else if (payload.type === 'approval/resolved' && payload.approvalId) replay.delete(`approval:${payload.approvalId}`)
|
|
@@ -2095,6 +2128,28 @@ function openModernSessionStream(ws, sessionId) {
|
|
|
2095
2128
|
type: 'open', streamId, endpoint: 'session/follow',
|
|
2096
2129
|
payload: { args: { request: { address: { kind: 'session', sessionId }, assistantStream: true } } },
|
|
2097
2130
|
}))
|
|
2131
|
+
ws.send(JSON.stringify({
|
|
2132
|
+
type: 'open', streamId: 'jobs:' + sessionId, endpoint: 'job/list',
|
|
2133
|
+
payload: { args: { request: { sessionId } } },
|
|
2134
|
+
}))
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
function applyModernProjection(sessionId, key, value, seq) {
|
|
2138
|
+
legacyPush('mux', { type: 'session/projection', sessionId, key, value, seq })
|
|
2139
|
+
if (key === 'inbox') {
|
|
2140
|
+
// DSH 0.2 stores queue/steer state in the durable Inbox projection.
|
|
2141
|
+
const items = ['next-turn', 'next-step'].flatMap(target =>
|
|
2142
|
+
(Array.isArray(value?.[target]) ? value[target] : []).filter(message => message?.id).map(message => ({
|
|
2143
|
+
id: message.id, placement: target === 'next-turn' ? 'queued' : 'context', message,
|
|
2144
|
+
})))
|
|
2145
|
+
legacyPush('mux', { type: 'session/queue', sessionId, items })
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
function applyModernJobFrame(sessionId, value) {
|
|
2150
|
+
if (value?.type === 'rows' && Array.isArray(value.jobs)) {
|
|
2151
|
+
legacyPush('mux', { type: 'session/jobs', sessionId, jobs: value.jobs })
|
|
2152
|
+
}
|
|
2098
2153
|
}
|
|
2099
2154
|
|
|
2100
2155
|
function applyModernControlFrame(value) {
|
|
@@ -2107,16 +2162,14 @@ function applyModernControlFrame(value) {
|
|
|
2107
2162
|
}
|
|
2108
2163
|
for (const [sessionId, block] of Object.entries(value.value?.projections || {})) {
|
|
2109
2164
|
for (const [key, projection] of Object.entries(block?.values || {})) {
|
|
2110
|
-
|
|
2165
|
+
applyModernProjection(sessionId, key, projection, block?.asOfSeq ?? 0)
|
|
2111
2166
|
}
|
|
2112
2167
|
}
|
|
2113
2168
|
return
|
|
2114
2169
|
}
|
|
2115
2170
|
if (value?.type === 'queue') legacyPush('mux', { type: 'session/queue', sessionId: value.sessionId, items: value.items || [] })
|
|
2116
2171
|
else if (value?.type === 'jobs') legacyPush('mux', { type: 'session/jobs', sessionId: value.sessionId, jobs: value.jobs || [] })
|
|
2117
|
-
else if (value?.type === 'projection')
|
|
2118
|
-
type: 'session/projection', sessionId: value.sessionId, key: value.key, value: value.value, seq: value.seq,
|
|
2119
|
-
})
|
|
2172
|
+
else if (value?.type === 'projection') applyModernProjection(value.sessionId, value.key, value.value, value.seq)
|
|
2120
2173
|
}
|
|
2121
2174
|
|
|
2122
2175
|
function applyModernWorkspaceFrame(value) {
|
|
@@ -2209,7 +2262,7 @@ function applyModernSessionFrame(sessionId, value) {
|
|
|
2209
2262
|
if (record?.event) legacyPush('mux', { type: 'session/event', sessionId, event: record.event })
|
|
2210
2263
|
}
|
|
2211
2264
|
for (const [key, projection] of Object.entries(value.projections?.values || {})) {
|
|
2212
|
-
|
|
2265
|
+
applyModernProjection(sessionId, key, projection, value.projections?.asOfSeq ?? value.cursor)
|
|
2213
2266
|
}
|
|
2214
2267
|
return
|
|
2215
2268
|
}
|
|
@@ -2282,7 +2335,11 @@ function applyModernRemoteEvent(ws, value) {
|
|
|
2282
2335
|
modernState.sessionCursors.delete(args[0])
|
|
2283
2336
|
modernState.assistantStreams.delete(args[0])
|
|
2284
2337
|
collectorReplay.mux.delete(`reasoning:${args[0]}`)
|
|
2338
|
+
collectorReplay.mux.delete(`session:${args[0]}`)
|
|
2339
|
+
collectorReplay.mux.delete(`queue:${args[0]}`)
|
|
2340
|
+
collectorReplay.mux.delete(`jobs:${args[0]}`)
|
|
2285
2341
|
try { ws.send(JSON.stringify({ type: 'cancel', streamId: 'session:' + args[0] })) } catch {}
|
|
2342
|
+
try { ws.send(JSON.stringify({ type: 'cancel', streamId: 'jobs:' + args[0] })) } catch {}
|
|
2286
2343
|
legacyPush('host', { type: 'host/session-removed', sessionId: args[0] })
|
|
2287
2344
|
} else if (value.event === 'api-session/status') {
|
|
2288
2345
|
legacyPush('host', { type: 'host/session-status', sessionId: args[0], running: !!args[1] })
|
|
@@ -2371,6 +2428,9 @@ function startModernEventCollector() {
|
|
|
2371
2428
|
const data = typeof ev.data === 'string' ? ev.data : Buffer.isBuffer(ev.data) ? ev.data.toString() : String(ev.data)
|
|
2372
2429
|
const frame = JSON.parse(data)
|
|
2373
2430
|
if (frame.type === 'error') {
|
|
2431
|
+
// Old generated-RPC Hosts may have no job namespace. This optional
|
|
2432
|
+
// roster must not degrade the working session and approval streams.
|
|
2433
|
+
if (frame.streamId?.startsWith('jobs:') && ['gateway/definition-unavailable', 'gateway/invocation-unavailable', 'gateway/service-unavailable', 'gateway/method-unavailable', 'method-unavailable', 'not-found'].includes(frame.error?.code)) return
|
|
2374
2434
|
const message = frame.error?.message || 'modern stream error'
|
|
2375
2435
|
for (const state of Object.values(eventCollectorState)) state.lastError = message
|
|
2376
2436
|
legacyPush('mux', { type: 'stream/error', error: frame.error || { message } })
|
|
@@ -2380,6 +2440,7 @@ function startModernEventCollector() {
|
|
|
2380
2440
|
if (frame.streamId === 'events') applyModernRemoteEvent(current, frame.value)
|
|
2381
2441
|
else if (frame.streamId === 'control') applyModernControlFrame(frame.value)
|
|
2382
2442
|
else if (frame.streamId === 'workspaces') applyModernWorkspaceFrame(frame.value)
|
|
2443
|
+
else if (frame.streamId.startsWith('jobs:')) applyModernJobFrame(frame.streamId.slice(5), frame.value)
|
|
2383
2444
|
else if (frame.streamId.startsWith('session:')) applyModernSessionFrame(frame.streamId.slice(8), frame.value)
|
|
2384
2445
|
} catch {}
|
|
2385
2446
|
}
|
|
@@ -4197,11 +4258,14 @@ function serveHandoff(req, res, url) {
|
|
|
4197
4258
|
fsJson(res, 400, { error: 'sessionId required' })
|
|
4198
4259
|
return
|
|
4199
4260
|
}
|
|
4261
|
+
// 白名单字段只接受字符串:对象/数字/数组等一律按空串处理,
|
|
4262
|
+
// 避免 String({}) 产出 '[object Object]' 之类垃圾落盘并广播到其他设备
|
|
4263
|
+
const str = (v) => typeof v === 'string' ? v : ''
|
|
4200
4264
|
handoffSave({
|
|
4201
4265
|
sessionId: sessionId.slice(0, 128),
|
|
4202
|
-
title:
|
|
4203
|
-
device:
|
|
4204
|
-
clientId:
|
|
4266
|
+
title: str(payload.title).slice(0, 200),
|
|
4267
|
+
device: str(payload.device).slice(0, 80),
|
|
4268
|
+
clientId: str(payload.clientId).slice(0, 96),
|
|
4205
4269
|
at: Date.now()
|
|
4206
4270
|
})
|
|
4207
4271
|
fsJson(res, 200, { ok: true })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-plugin",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "DSH Remote 官方 bundle 插件:DSH 左侧原生边栏入口 + 右侧抽屉内嵌管理控制台;内置网关随 DSH 自动启停(systemd 独立单元),抽屉直显令牌与设备监控;网关提供 /fs/* 文件传输端点(列表/断点下载/上传),配合 Android App 远程操控会话/审批/提问/goal 与文件互传(多服务器测速切换、聊天记录离线缓存)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|
|
@@ -501,9 +501,11 @@ function closeNotesModal() {
|
|
|
501
501
|
}
|
|
502
502
|
async function checkNotesOnStart() {
|
|
503
503
|
try {
|
|
504
|
-
const res = await fetch('../update.json?t=' + Date.now())
|
|
505
|
-
if (!res.ok) return
|
|
506
|
-
|
|
504
|
+
const res = await fetch('../update.json?t=' + Date.now(), { cache: 'no-store' })
|
|
505
|
+
if (!res.ok || res.redirected || !String(res.headers.get('content-type') || '').includes('application/json')) return
|
|
506
|
+
const info = await res.json()
|
|
507
|
+
if (!info || typeof info !== 'object' || !info.version) return
|
|
508
|
+
openNotesModal(info)
|
|
507
509
|
} catch {}
|
|
508
510
|
}
|
|
509
511
|
async function submitFeedback() {
|
package/public/update.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.7.
|
|
2
|
+
"version": "0.7.1",
|
|
3
3
|
"apkUrl": "dsh-remote.apk",
|
|
4
|
-
"sha256": "
|
|
5
|
-
"releasedAt": "2026-
|
|
6
|
-
"notes": "
|
|
4
|
+
"sha256": "7ca47a78d152754d4991d5605d8e368ee0e4a20b7bfe1a90055d72c04069cff1",
|
|
5
|
+
"releasedAt": "2026-10-02T13:59:16.334Z",
|
|
6
|
+
"notes": "适配 DSH 0.2.0-rc.2 的 Inbox 队列、后台任务流与子代理目录,修复子代理接口 404 和归档后刷新状态滞后,保留旧版实时通道兼容;修复 Desktop 插件面板被标题栏遮挡、收起后阴影残留及更新说明响应校验。合入鸿蒙插件中心、长按发送和主题改进,并修复切换连接时的旧确认卡、误发送及迟到回执问题;鸿蒙本次仅更新源码,暂不提供安装包。",
|
|
7
7
|
"history": [
|
|
8
|
+
{
|
|
9
|
+
"version": "0.7.1",
|
|
10
|
+
"notes": "适配 DSH 0.2.0-rc.2 的 Inbox 队列、后台任务流与子代理目录,修复子代理接口 404 和归档后刷新状态滞后,保留旧版实时通道兼容;修复 Desktop 插件面板被标题栏遮挡、收起后阴影残留及更新说明响应校验。合入鸿蒙插件中心、长按发送和主题改进,并修复切换连接时的旧确认卡、误发送及迟到回执问题;鸿蒙本次仅更新源码,暂不提供安装包。"
|
|
11
|
+
},
|
|
8
12
|
{
|
|
9
13
|
"version": "0.7.0",
|
|
10
14
|
"notes": "0.7.0:Windows 上未注册为服务的 DSH 可由 Remote 网关按已检查的 CLI 启动配置受控启动;已有 Windows 服务仍优先使用,网关只重启自己启动并持有句柄的进程。手机发送图片遇到 DSH 明确拒绝(当前模型不支持图片)时,可通过已启用的 dsh-image-vision 插件桥接附件;桥接失败不会提交不完整消息。保留长按插队发送、跨设备接续、鸿蒙客户端源码与平板分栏、网关 WebSocket Ping 分帧修复等 0.7.0 RC 功能。本次提供 Android APK 与 Windows/Linux 网关,不提供鸿蒙 HAP。"
|
|
@@ -40,10 +44,6 @@
|
|
|
40
44
|
{
|
|
41
45
|
"version": "0.6.19",
|
|
42
46
|
"notes": "0.6.19:全面优化手机端、桌面端、管理页和插件页的布局、动效与响应式体验,并支持减少动态效果偏好;重做蓝色、草原、音乐厅等主题的明暗层级和文字图标对比度,新增利落的黑白配色;统一主页图标配色并修正设置齿轮中心孔偏移;目标面板支持收起为始终可见的侧边入口,保留运行状态、可随时恢复,并按会话与目标在本地记忆。"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"version": "0.6.18",
|
|
46
|
-
"notes": "0.6.18:新增独立 Token 量趋势统计并保留原有费用趋势;服务器连接身份改为地址加令牌,支持同地址不同令牌并兼容旧配置;投票在网关不可达时尝试直连公网收集器托底(需收集器允许 App/Web CORS);修复 Windows 下 gateway-stats 调用 zstd 时弹出终端窗口的问题,并补充回归测试;同步完善相关模块文档。"
|
|
47
47
|
}
|
|
48
48
|
]
|
|
49
49
|
}
|
package/public/version.json
CHANGED