dsh-device-fleet 0.1.0-dev.22 → 0.1.0-dev.23

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/client.js CHANGED
@@ -1230,14 +1230,195 @@ window.__ModuleLoader__.load({
1230
1230
  );
1231
1231
  }
1232
1232
 
1233
- function RoomView({ sessionId }) {
1234
- return h('div', { style: { height: '100%', overflow: 'auto', padding: '14px 16px', boxSizing: 'border-box' } },
1235
- h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 } },
1236
- h('strong', { style: { fontSize: 15 } }, '房间'),
1237
- h('span', { style: { fontSize: 11, opacity: 0.55 } }, '多设备成员共享的会话'),
1233
+ function roomAvatar(name, tone) {
1234
+ const text = String(name ?? '?').trim().slice(0, 1).toUpperCase() || '?'
1235
+ return h('div', {
1236
+ style: {
1237
+ width: 26,
1238
+ height: 26,
1239
+ borderRadius: '50%',
1240
+ flex: '0 0 26px',
1241
+ display: 'flex',
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)',
1248
+ border: '1px solid var(--dsw-alias-border-l1, #333)',
1249
+ },
1250
+ }, text)
1251
+ }
1252
+
1253
+ // Room conversation rendered as a chat: agents on the left with names and
1254
+ // avatars, the user on the right. This is the Room's presentation instead of
1255
+ // injecting participant speech into the host's ordinary Chat.
1256
+ function RoomChatPane({ roomId }) {
1257
+ const [detail, setDetail] = useState(null);
1258
+ const [missing, setMissing] = useState(false);
1259
+ const [error, setError] = useState('');
1260
+ const [sendText, setSendText] = useState('');
1261
+ const [sendTo, setSendTo] = useState([]);
1262
+ const [status, setStatus] = useState('');
1263
+ const [showBoard, setShowBoard] = useState(false);
1264
+ const [taskTitle, setTaskTitle] = useState('');
1265
+ const [taskOwner, setTaskOwner] = useState('');
1266
+
1267
+ const load = async () => {
1268
+ if (!roomId) return;
1269
+ try {
1270
+ const response = await fetch('/device-fleet/room?id=' + encodeURIComponent(roomId), { headers: { accept: 'application/json' } });
1271
+ if (response.status === 404) {
1272
+ setMissing(true);
1273
+ setDetail(null);
1274
+ return;
1275
+ }
1276
+ if (!response.ok) throw new Error('HTTP ' + response.status);
1277
+ const data = await response.json();
1278
+ setDetail(data.room ?? null);
1279
+ setMissing(false);
1280
+ setError('');
1281
+ } catch (problem) {
1282
+ setError(String(problem.message ?? problem));
1283
+ }
1284
+ };
1285
+
1286
+ useEffect(() => {
1287
+ void load();
1288
+ const timer = setInterval(load, 2000);
1289
+ return () => clearInterval(timer);
1290
+ }, [roomId]);
1291
+
1292
+ const act = async (label, action) => {
1293
+ setStatus(label + '…');
1294
+ try {
1295
+ await action();
1296
+ setStatus(label + '成功');
1297
+ await load();
1298
+ } catch (problem) {
1299
+ setStatus(label + '失败:' + String(problem.message ?? problem));
1300
+ }
1301
+ };
1302
+
1303
+ if (!roomId) return null;
1304
+ if (missing) {
1305
+ return h('div', { style: { padding: 16, opacity: 0.75, fontSize: 12 } }, '这个会话还不是 Room。点击输入框下方的「升级为 Room」即可创建。');
1306
+ }
1307
+ if (!detail) return h('div', { style: { padding: 16, opacity: 0.7, fontSize: 12 } }, error ? '房间加载失败:' + error : '加载房间…');
1308
+
1309
+ const nameOf = (memberId) => detail.members.find((member) => member.memberId === memberId)?.name ?? memberId
1310
+ const participants = detail.members.filter((member) => member.role !== 'host')
1311
+ const defaultRecipients = participants.length > 0 ? participants.map((member) => member.memberId) : detail.members.map((member) => member.memberId)
1312
+ const recipients = sendTo.length > 0 ? sendTo : defaultRecipients
1313
+ const send = () =>
1314
+ act('发送', async () => {
1315
+ if (sendText.trim() === '') throw new Error('消息不能为空')
1316
+ await postJson('/device-fleet/room/send', { roomId, to: recipients, text: sendText })
1317
+ setSendText('')
1318
+ })
1319
+
1320
+ const messages = detail.messages.map((message) => {
1321
+ const fromUser = message.from?.kind === 'user'
1322
+ const author = fromUser ? '你' : message.from?.name ?? 'agent'
1323
+ const targets = (message.to ?? []).map(nameOf).join(', ')
1324
+ return h('div', { key: message.id, style: { display: 'flex', justifyContent: fromUser ? 'flex-end' : 'flex-start', gap: 8, margin: '10px 0' } },
1325
+ fromUser ? null : roomAvatar(author, 'agent'),
1326
+ h('div', { style: { maxWidth: '74%' } },
1327
+ fromUser ? null : h('div', { style: { fontSize: 11, opacity: 0.6, marginBottom: 2 } }, author + (targets ? ' · → ' + targets : '')),
1328
+ h('div', {
1329
+ style: {
1330
+ background: fromUser ? '#2f6fed' : 'var(--dsw-alias-bg-layer-2, #262626)',
1331
+ color: fromUser ? '#fff' : 'var(--dsw-alias-label-primary, #e8e8e8)',
1332
+ padding: '8px 12px',
1333
+ borderRadius: 12,
1334
+ whiteSpace: 'pre-wrap',
1335
+ fontSize: 13,
1336
+ lineHeight: 1.5,
1337
+ },
1338
+ }, message.text),
1339
+ h('div', { style: { fontSize: 10, opacity: 0.45, marginTop: 2, textAlign: fromUser ? 'right' : 'left' } }, fmtTime(message.createdAt)),
1340
+ ),
1341
+ fromUser ? roomAvatar('你', 'user') : null,
1342
+ )
1343
+ })
1344
+
1345
+ return h('div', { style: { height: '100%', display: 'flex', flexDirection: 'column', padding: '12px 16px', boxSizing: 'border-box' } },
1346
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 8 } },
1347
+ h('strong', { style: { fontSize: 15 } }, 'Room'),
1348
+ ...detail.members.map((member) =>
1349
+ h('span', { key: member.memberId, style: { display: 'flex', alignItems: 'center', gap: 5, fontSize: 11, opacity: 0.8 } },
1350
+ roomAvatar(member.name ?? member.memberId, member.role === 'host' ? 'user' : 'agent'),
1351
+ member.name + (member.role === 'host' ? '(host)' : ''),
1352
+ ),
1353
+ ),
1354
+ h('span', { style: { flex: 1 } }),
1355
+ status ? h('span', { style: { fontSize: 11, opacity: 0.6 } }, status) : null,
1356
+ smallButton(showBoard ? '收起看板' : '看板', () => setShowBoard((value) => !value), 'normal'),
1357
+ smallButton('关闭房间', () => { void act('关闭房间', () => postJson('/device-fleet/room/close', { roomId })) }, 'deny'),
1238
1358
  ),
1239
- h(RoomPane, { roomId: sessionId }),
1240
- );
1359
+ h('div', { style: { flex: 1, overflow: 'auto', minHeight: 120 } }, messages.length === 0 ? h('div', { style: { opacity: 0.6, fontSize: 12 } }, '还没有消息') : messages),
1360
+ showBoard
1361
+ ? h('div', { style: { ...cardStyle, marginTop: 8 } },
1362
+ h('div', { style: sectionTitle }, '看板'),
1363
+ detail.tasks.length === 0 ? h('div', { style: { opacity: 0.65, fontSize: 12 } }, '暂无任务') : detail.tasks.map((task) =>
1364
+ h('div', { key: task.id, style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', fontSize: 12 } },
1365
+ h('span', { style: { flex: 1 } }, task.title + ' · ' + nameOf(task.owner)),
1366
+ h('select', {
1367
+ value: task.status,
1368
+ onChange: (event) => void act('更新任务', () => postJson('/device-fleet/room/task', { roomId, action: 'update', id: task.id, status: event.target.value })),
1369
+ style: { height: 24, borderRadius: 5, border: '1px solid var(--dsw-alias-border-l1, #333)', background: 'var(--dsw-alias-bg-layer-2, #262626)', color: 'inherit', fontSize: 11 },
1370
+ }, ...['pending', 'running', 'done', 'blocked'].map((value) => h('option', { key: value, value }, value))),
1371
+ ),
1372
+ ),
1373
+ h('div', { style: { display: 'flex', gap: 6, marginTop: 8, flexWrap: 'wrap' } },
1374
+ h('input', { type: 'text', value: taskTitle, onChange: (event) => setTaskTitle(event.target.value), placeholder: '新任务', style: { ...controlStyleWidth(160), height: 26, fontSize: 12 } }),
1375
+ h('select', { value: taskOwner, onChange: (event) => setTaskOwner(event.target.value), style: { ...controlStyleWidth(150), height: 26, fontSize: 12 } },
1376
+ h('option', { value: '' }, 'owner'),
1377
+ ...detail.members.map((member) => h('option', { key: member.memberId, value: member.memberId }, member.name)),
1378
+ ),
1379
+ smallButton('添加', () => void act('添加任务', () => postJson('/device-fleet/room/task', { roomId, action: 'add', title: taskTitle, owner: taskOwner })), 'allow'),
1380
+ ),
1381
+ )
1382
+ : null,
1383
+ h('div', { style: { ...cardStyle, marginTop: 8 } },
1384
+ h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '4px 12px', marginBottom: 6 } },
1385
+ h('span', { style: { fontSize: 11, opacity: 0.6, alignSelf: 'center' } }, '发给'),
1386
+ ...detail.members.filter((member) => member.role !== 'host').map((member) =>
1387
+ h('label', { key: member.memberId, style: { display: 'flex', alignItems: 'center', gap: 4, fontSize: 12 } },
1388
+ h('input', {
1389
+ type: 'checkbox',
1390
+ checked: recipients.includes(member.memberId),
1391
+ onChange: (event) => {
1392
+ setSendTo((current) => {
1393
+ const base = current.length > 0 ? current : defaultRecipients
1394
+ return event.target.checked ? [...new Set([...base, member.memberId])] : base.filter((id) => id !== member.memberId)
1395
+ })
1396
+ },
1397
+ }),
1398
+ member.name,
1399
+ ),
1400
+ ),
1401
+ ),
1402
+ h('textarea', {
1403
+ value: sendText,
1404
+ onChange: (event) => setSendText(event.target.value),
1405
+ onKeyDown: (event) => {
1406
+ if (event.key === 'Enter' && !event.shiftKey) {
1407
+ event.preventDefault()
1408
+ void send()
1409
+ }
1410
+ },
1411
+ rows: 2,
1412
+ placeholder: '发消息给房间成员(Enter 发送,Shift+Enter 换行)',
1413
+ 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
+ }),
1415
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', marginTop: 6 } }, smallButton('发送', () => void send(), 'allow')),
1416
+ ),
1417
+ )
1418
+ }
1419
+
1420
+ function RoomView({ sessionId }) {
1421
+ return h('div', { style: { height: '100%' } }, h(RoomChatPane, { roomId: sessionId }));
1241
1422
  }
1242
1423
 
1243
1424
  return {
@@ -117,4 +117,10 @@ dsh plugin --profile desktop add dsh-device-fleet@dev
117
117
  - Fleet 控制台保留为设备/配对/审批/配置管理入口。
118
118
  ## 0.1.0-dev.21(Room 回复地址修复)
119
119
 
120
- - 参与者被用户消息唤醒时,回复引导里的目标从无效的 "host" 修正为真实 Host member id(`host:<sessionId>`),修复 worker 回执发给自己/失败的问题。
120
+ - 参与者被用户消息唤醒时,回复引导里的目标从无效的 "host" 修正为真实 Host member id(`host:<sessionId>`),修复 worker 回执发给自己/失败的问题。
121
+ ## 0.1.0-dev.22(Room 独立视图,停止 host 转述)
122
+
123
+ - Session 内新增「房间」Conversation View(`conversation.view`,label 房间):展示成员、时间线(含发言者/收件人)、看板与房间操作,输入默认发给非 host 参与者;
124
+ - 移除 Chat 输入的隐式 Room 广播:普通 Chat 只属于 host agent,Room 交流在「房间」页进行,避免用户在 Chat 里说话被自动转发、或 host agent 转述 worker 回复;
125
+ - host 不再接收参与者回复的 Chat 注入(参与者回复只进入房间时间线);
126
+ - 输入框下方成员条提示「房间消息在『房间』页交流」。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-device-fleet",
3
- "version": "0.1.0-dev.22",
3
+ "version": "0.1.0-dev.23",
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",