dsh-device-fleet 0.1.0-dev.23 → 0.1.0-dev.25
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/assets/avatars/host.png +0 -0
- package/assets/avatars/user.png +0 -0
- package/assets/avatars/worker.png +0 -0
- package/client.js +47 -28
- package/docs/notes/dev-release.md +12 -1
- package/package.json +2 -1
- package/src/host/avatar-web.js +52 -0
- package/src/host/index.js +1 -0
- package/src/plugin/index.js +8 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/client.js
CHANGED
|
@@ -1231,23 +1231,21 @@ window.__ModuleLoader__.load({
|
|
|
1231
1231
|
}
|
|
1232
1232
|
|
|
1233
1233
|
function roomAvatar(name, tone) {
|
|
1234
|
-
const
|
|
1235
|
-
return h('
|
|
1234
|
+
const kind = tone === 'user' ? 'user' : String(name ?? '') === 'host' ? 'host' : 'worker'
|
|
1235
|
+
return h('img', {
|
|
1236
|
+
src: '/device-fleet/avatar/' + kind,
|
|
1237
|
+
alt: String(name ?? kind),
|
|
1238
|
+
draggable: false,
|
|
1236
1239
|
style: {
|
|
1237
1240
|
width: 26,
|
|
1238
1241
|
height: 26,
|
|
1239
1242
|
borderRadius: '50%',
|
|
1240
1243
|
flex: '0 0 26px',
|
|
1241
|
-
|
|
1242
|
-
alignItems: 'center',
|
|
1243
|
-
justifyContent: 'center',
|
|
1244
|
-
fontSize: 12,
|
|
1245
|
-
fontWeight: 600,
|
|
1246
|
-
background: tone === 'user' ? '#2f6fed' : 'var(--dsw-alias-bg-layer-2, #262626)',
|
|
1247
|
-
color: tone === 'user' ? '#fff' : 'var(--dsw-alias-label-primary, #e8e8e8)',
|
|
1244
|
+
objectFit: 'cover',
|
|
1248
1245
|
border: '1px solid var(--dsw-alias-border-l1, #333)',
|
|
1246
|
+
background: 'var(--dsw-alias-bg-layer-2, #262626)',
|
|
1249
1247
|
},
|
|
1250
|
-
}
|
|
1248
|
+
})
|
|
1251
1249
|
}
|
|
1252
1250
|
|
|
1253
1251
|
// Room conversation rendered as a chat: agents on the left with names and
|
|
@@ -1310,6 +1308,11 @@ window.__ModuleLoader__.load({
|
|
|
1310
1308
|
const participants = detail.members.filter((member) => member.role !== 'host')
|
|
1311
1309
|
const defaultRecipients = participants.length > 0 ? participants.map((member) => member.memberId) : detail.members.map((member) => member.memberId)
|
|
1312
1310
|
const recipients = sendTo.length > 0 ? sendTo : defaultRecipients
|
|
1311
|
+
const mentionMatch = /(?:^|\s)@([^\s@]*)$/.exec(sendText)
|
|
1312
|
+
const mentionInfo = mentionMatch ? { query: mentionMatch[1], start: sendText.length - mentionMatch[1].length - 1 } : null
|
|
1313
|
+
const mentionCandidates = mentionInfo
|
|
1314
|
+
? detail.members.filter((member) => String(member.name ?? '').toLowerCase().includes(mentionInfo.query.toLowerCase()))
|
|
1315
|
+
: []
|
|
1313
1316
|
const send = () =>
|
|
1314
1317
|
act('发送', async () => {
|
|
1315
1318
|
if (sendText.trim() === '') throw new Error('消息不能为空')
|
|
@@ -1381,23 +1384,19 @@ window.__ModuleLoader__.load({
|
|
|
1381
1384
|
)
|
|
1382
1385
|
: null,
|
|
1383
1386
|
h('div', { style: { ...cardStyle, marginTop: 8 } },
|
|
1384
|
-
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap:
|
|
1385
|
-
h('span', { style: { fontSize: 11, opacity: 0.6
|
|
1386
|
-
|
|
1387
|
-
h('
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
setSendTo((current) =>
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
}),
|
|
1398
|
-
member.name,
|
|
1399
|
-
),
|
|
1400
|
-
),
|
|
1387
|
+
h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, alignItems: 'center', marginBottom: 6 } },
|
|
1388
|
+
h('span', { style: { fontSize: 11, opacity: 0.6 } }, '发给'),
|
|
1389
|
+
sendTo.length === 0
|
|
1390
|
+
? h('span', { style: { fontSize: 11, opacity: 0.8 } }, '所有人')
|
|
1391
|
+
: sendTo.map((memberId) =>
|
|
1392
|
+
h('span', {
|
|
1393
|
+
key: memberId,
|
|
1394
|
+
style: { fontSize: 11, padding: '1px 7px', borderRadius: 999, border: '1px solid var(--dsw-alias-accent, #2f6fed)', color: 'var(--dsw-alias-accent, #2f6fed)', cursor: 'pointer' },
|
|
1395
|
+
onClick: () => setSendTo((current) => current.filter((id) => id !== memberId)),
|
|
1396
|
+
}, '@' + nameOf(memberId) + ' ×'),
|
|
1397
|
+
),
|
|
1398
|
+
h('span', { style: { flex: 1 } }),
|
|
1399
|
+
h('span', { style: { fontSize: 10, opacity: 0.45 } }, '输入 @ 点名'),
|
|
1401
1400
|
),
|
|
1402
1401
|
h('textarea', {
|
|
1403
1402
|
value: sendText,
|
|
@@ -1409,9 +1408,29 @@ window.__ModuleLoader__.load({
|
|
|
1409
1408
|
}
|
|
1410
1409
|
},
|
|
1411
1410
|
rows: 2,
|
|
1412
|
-
placeholder: '
|
|
1411
|
+
placeholder: '发消息给房间成员;输入 @ 可点名(Enter 发送,Shift+Enter 换行)',
|
|
1413
1412
|
style: { width: '100%', boxSizing: 'border-box', padding: '7px 9px', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l1, #333)', background: 'var(--dsw-alias-bg-layer-2, #262626)', color: 'inherit', fontSize: 13, resize: 'vertical', fontFamily: 'inherit' },
|
|
1414
1413
|
}),
|
|
1414
|
+
mentionInfo && mentionCandidates.length > 0
|
|
1415
|
+
? h('div', { style: { marginTop: 6, border: '1px solid var(--dsw-alias-border-l1, #333)', borderRadius: 8, background: 'var(--dsw-alias-bg-layer-2, #262626)', overflow: 'hidden' } },
|
|
1416
|
+
...mentionCandidates.map((member) =>
|
|
1417
|
+
h('button', {
|
|
1418
|
+
key: member.memberId,
|
|
1419
|
+
type: 'button',
|
|
1420
|
+
onClick: () => {
|
|
1421
|
+
setSendTo((current) => [...new Set([...current, member.memberId])])
|
|
1422
|
+
setSendText((text) => {
|
|
1423
|
+
const match = /(?:^|\s)@([^\s@]*)$/.exec(text)
|
|
1424
|
+
if (!match) return text
|
|
1425
|
+
const start = text.length - match[1].length - 1
|
|
1426
|
+
return text.slice(0, start).replace(/\s+$/, '')
|
|
1427
|
+
})
|
|
1428
|
+
},
|
|
1429
|
+
style: { display: 'flex', alignItems: 'center', gap: 6, width: '100%', padding: '6px 9px', background: 'transparent', border: 'none', color: 'inherit', cursor: 'pointer', fontSize: 12, textAlign: 'left' },
|
|
1430
|
+
}, roomAvatar(member.name ?? member.memberId, member.role === 'host' ? 'user' : 'agent'), member.name + (member.role === 'host' ? '(host)' : '')),
|
|
1431
|
+
),
|
|
1432
|
+
)
|
|
1433
|
+
: null,
|
|
1415
1434
|
h('div', { style: { display: 'flex', justifyContent: 'flex-end', marginTop: 6 } }, smallButton('发送', () => void send(), 'allow')),
|
|
1416
1435
|
),
|
|
1417
1436
|
)
|
|
@@ -123,4 +123,15 @@ dsh plugin --profile desktop add dsh-device-fleet@dev
|
|
|
123
123
|
- Session 内新增「房间」Conversation View(`conversation.view`,label 房间):展示成员、时间线(含发言者/收件人)、看板与房间操作,输入默认发给非 host 参与者;
|
|
124
124
|
- 移除 Chat 输入的隐式 Room 广播:普通 Chat 只属于 host agent,Room 交流在「房间」页进行,避免用户在 Chat 里说话被自动转发、或 host agent 转述 worker 回复;
|
|
125
125
|
- host 不再接收参与者回复的 Chat 注入(参与者回复只进入房间时间线);
|
|
126
|
-
- 输入框下方成员条提示「房间消息在『房间』页交流」。
|
|
126
|
+
- 输入框下方成员条提示「房间消息在『房间』页交流」。
|
|
127
|
+
## 0.1.0-dev.23(Room 聊天气泡视图)
|
|
128
|
+
|
|
129
|
+
- 「房间」Conversation View 重做为聊天式:agent 消息在左(头像 + 名字 + 收件人),用户消息在右;时间戳、Enter 发送、收件人勾选;
|
|
130
|
+
- 参与者消息原文直接作为左侧气泡展示,不再进入 host 的普通 Chat;
|
|
131
|
+
- 看板可展开/收起,支持任务状态更新与添加。
|
|
132
|
+
## 0.1.0-dev.24(Room @点名)
|
|
133
|
+
|
|
134
|
+
- 房间输入框支持输入 `@` 弹出成员自动补全;选中成员变为「发给」chip,可点击取消;
|
|
135
|
+
- 无点名时默认发给所有非 host 参与者;被点名成员才唤醒;
|
|
136
|
+
- 聊天气泡显示发送者与收件人(worker-c · → host),用户侧为右气泡、agent 侧为左气泡;
|
|
137
|
+
- 后续:把 Room 状态下的 Chat 替换为这套界面(插件增量,不卸载系统 Chat)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-device-fleet",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.25",
|
|
4
4
|
"description": "DeepSeek Harness device fleet: enroll devices, direct-call remote tools, shared Rooms with lazy-woken agents, and delegated subagents across devices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/plugin/index.js",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"files": [
|
|
41
41
|
"src",
|
|
42
42
|
"client.js",
|
|
43
|
+
"assets",
|
|
43
44
|
"cordis.patch.yml",
|
|
44
45
|
"README.md",
|
|
45
46
|
"CONTEXT.md",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Agent avatars used by the Room conversation (dev.25). Files live in the
|
|
2
|
+
// package's assets/avatars directory and are served on exact same-origin routes
|
|
3
|
+
// so the browser can render them without an auth-bypassing asset surface.
|
|
4
|
+
import { readFileSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
export const AVATARS = {
|
|
8
|
+
host: 'host.png',
|
|
9
|
+
worker: 'worker.png',
|
|
10
|
+
user: 'user.png',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} options
|
|
15
|
+
* @param {object} options.webServer ctx.webServer
|
|
16
|
+
* @param {string} options.dir directory holding the avatar PNG files
|
|
17
|
+
* @returns {() => void} disposer
|
|
18
|
+
*/
|
|
19
|
+
export function registerAvatarRoutes({ webServer, dir, logger = null }) {
|
|
20
|
+
if (!webServer?.register || !dir) return () => {}
|
|
21
|
+
const disposers = []
|
|
22
|
+
for (const [name, file] of Object.entries(AVATARS)) {
|
|
23
|
+
disposers.push(
|
|
24
|
+
webServer.register({
|
|
25
|
+
kind: 'exact',
|
|
26
|
+
path: `/device-fleet/avatar/${name}`,
|
|
27
|
+
handler: async (req, res) => {
|
|
28
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
29
|
+
res.statusCode = 405
|
|
30
|
+
res.end()
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const data = readFileSync(join(dir, file))
|
|
35
|
+
res.statusCode = 200
|
|
36
|
+
res.setHeader('content-type', 'image/png')
|
|
37
|
+
res.setHeader('cache-control', 'public, max-age=3600')
|
|
38
|
+
res.setHeader('content-length', String(data.length))
|
|
39
|
+
res.end(req.method === 'HEAD' ? undefined : data)
|
|
40
|
+
} catch {
|
|
41
|
+
logger?.warn?.(`[device-fleet] avatar missing: ${file}`)
|
|
42
|
+
res.statusCode = 404
|
|
43
|
+
res.end()
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
return () => {
|
|
50
|
+
for (const dispose of disposers) dispose()
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/host/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from './approval-inbox.js'
|
|
|
8
8
|
export * from './approval-web.js'
|
|
9
9
|
export * from './ui-web.js'
|
|
10
10
|
export * from './config-web.js'
|
|
11
|
+
export * from './avatar-web.js'
|
|
11
12
|
export * from './runtime-config.js'
|
|
12
13
|
export * from './room-service.js'
|
|
13
14
|
export * from './tools.js'
|
package/src/plugin/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import { RoomService } from '../host/room-service.js'
|
|
|
20
20
|
import { registerApprovalRoutes } from '../host/approval-web.js'
|
|
21
21
|
import { registerUiRoutes } from '../host/ui-web.js'
|
|
22
22
|
import { registerConfigRoutes } from '../host/config-web.js'
|
|
23
|
+
import { registerAvatarRoutes } from '../host/avatar-web.js'
|
|
23
24
|
import { RuntimeConfig, deepMerge } from '../host/runtime-config.js'
|
|
24
25
|
import { ExecutionSlot, DEFAULT_SHELL_TOOL } from '../host/execution-slot.js'
|
|
25
26
|
import { ToolPolicy } from '../host/tool-policy.js'
|
|
@@ -280,6 +281,13 @@ export function apply(ctx, profileConfig) {
|
|
|
280
281
|
}
|
|
281
282
|
let pairingRef = null
|
|
282
283
|
if (webServer) {
|
|
284
|
+
ctx.effect(() =>
|
|
285
|
+
registerAvatarRoutes({
|
|
286
|
+
webServer,
|
|
287
|
+
dir: fileURLToPath(new URL('../../assets/avatars/', import.meta.url)),
|
|
288
|
+
logger,
|
|
289
|
+
}),
|
|
290
|
+
)
|
|
283
291
|
ctx.effect(() => registerApprovalRoutes({ webServer, inbox, logger }))
|
|
284
292
|
ctx.effect(() =>
|
|
285
293
|
registerConfigRoutes({
|