dsh-device-fleet 0.1.0-dev.21 → 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 +287 -5
- package/docs/notes/dev-release.md +10 -1
- package/package.json +1 -1
- package/src/host/room-service.js +3 -0
- package/src/plugin/index.js +3 -32
package/client.js
CHANGED
|
@@ -608,7 +608,9 @@ window.__ModuleLoader__.load({
|
|
|
608
608
|
function renderRoomDetail(detail, ctl) {
|
|
609
609
|
if (!detail) return h('div', { style: { opacity: 0.65, fontSize: 12 } }, '加载房间…');
|
|
610
610
|
const memberIds = detail.members.map((member) => member.memberId);
|
|
611
|
-
const
|
|
611
|
+
const participantIds = memberIds.filter((id) => !id.startsWith('host:'));
|
|
612
|
+
const defaultRecipients = participantIds.length > 0 ? participantIds : memberIds;
|
|
613
|
+
const recipientIds = Array.isArray(ctl.sendTo) && ctl.sendTo.length > 0 ? ctl.sendTo : defaultRecipients;
|
|
612
614
|
return h(
|
|
613
615
|
'div',
|
|
614
616
|
null,
|
|
@@ -636,7 +638,7 @@ window.__ModuleLoader__.load({
|
|
|
636
638
|
checked: recipientIds.includes(member.memberId),
|
|
637
639
|
onChange: (event) => {
|
|
638
640
|
ctl.setSendTo((current) => {
|
|
639
|
-
const base = current.length > 0 ? current :
|
|
641
|
+
const base = current.length > 0 ? current : defaultRecipients
|
|
640
642
|
return event.target.checked ? [...new Set([...base, member.memberId])] : base.filter((id) => id !== member.memberId)
|
|
641
643
|
})
|
|
642
644
|
},
|
|
@@ -1138,10 +1140,287 @@ window.__ModuleLoader__.load({
|
|
|
1138
1140
|
h('span', null, room.members.length + ' 名成员'),
|
|
1139
1141
|
names.length > 0 ? h('span', null, '· ' + names.join(', ')) : h('span', null, '· 仅 host'),
|
|
1140
1142
|
h('span', { style: { flex: 1 } }),
|
|
1141
|
-
h('span', { style: { opacity: 0.55 } }, '
|
|
1143
|
+
h('span', { style: { opacity: 0.55 } }, '房间消息在「房间」页交流'),
|
|
1142
1144
|
);
|
|
1143
1145
|
}
|
|
1144
1146
|
|
|
1147
|
+
// Session-native Room view: the Room's transcript, members and board live
|
|
1148
|
+
// in their own Conversation View instead of the normal Chat stream.
|
|
1149
|
+
function RoomPane({ roomId }) {
|
|
1150
|
+
const [detail, setDetail] = useState(null);
|
|
1151
|
+
const [missing, setMissing] = useState(false);
|
|
1152
|
+
const [error, setError] = useState('');
|
|
1153
|
+
const [sendText, setSendText] = useState('');
|
|
1154
|
+
const [sendTo, setSendTo] = useState([]);
|
|
1155
|
+
const [taskTitle, setTaskTitle] = useState('');
|
|
1156
|
+
const [taskOwner, setTaskOwner] = useState('');
|
|
1157
|
+
const [status, setStatus] = useState('');
|
|
1158
|
+
|
|
1159
|
+
const load = async () => {
|
|
1160
|
+
if (!roomId) return;
|
|
1161
|
+
try {
|
|
1162
|
+
const response = await fetch('/device-fleet/room?id=' + encodeURIComponent(roomId), { headers: { accept: 'application/json' } });
|
|
1163
|
+
if (response.status === 404) {
|
|
1164
|
+
setMissing(true);
|
|
1165
|
+
setDetail(null);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
1169
|
+
const data = await response.json();
|
|
1170
|
+
setDetail(data.room ?? null);
|
|
1171
|
+
setMissing(false);
|
|
1172
|
+
setError('');
|
|
1173
|
+
} catch (problem) {
|
|
1174
|
+
setError(String(problem.message ?? problem));
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
useEffect(() => {
|
|
1179
|
+
void load();
|
|
1180
|
+
const timer = setInterval(load, 2500);
|
|
1181
|
+
return () => clearInterval(timer);
|
|
1182
|
+
}, [roomId]);
|
|
1183
|
+
|
|
1184
|
+
const act = async (label, action) => {
|
|
1185
|
+
setStatus(label + '…');
|
|
1186
|
+
try {
|
|
1187
|
+
await action();
|
|
1188
|
+
setStatus(label + '成功');
|
|
1189
|
+
await load();
|
|
1190
|
+
} catch (problem) {
|
|
1191
|
+
setStatus(label + '失败:' + String(problem.message ?? problem));
|
|
1192
|
+
}
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
if (!roomId) return null;
|
|
1196
|
+
if (missing) {
|
|
1197
|
+
return h('div', { style: { opacity: 0.75, fontSize: 12 } }, '这个会话还不是 Room。在下方输入框旁点击「升级为 Room」,或让 agent 执行 room_create。');
|
|
1198
|
+
}
|
|
1199
|
+
if (!detail) return h('div', { style: { opacity: 0.7, fontSize: 12 } }, error ? '房间加载失败:' + error : '加载房间…');
|
|
1200
|
+
|
|
1201
|
+
const ctl = {
|
|
1202
|
+
sendText,
|
|
1203
|
+
setSendText,
|
|
1204
|
+
sendTo,
|
|
1205
|
+
setSendTo,
|
|
1206
|
+
sendRoomMessage: () =>
|
|
1207
|
+
act('发送', async () => {
|
|
1208
|
+
if (sendText.trim() === '') throw new Error('消息不能为空');
|
|
1209
|
+
await postJson('/device-fleet/room/send', { roomId, to: sendTo, text: sendText });
|
|
1210
|
+
setSendText('');
|
|
1211
|
+
}),
|
|
1212
|
+
taskTitle,
|
|
1213
|
+
setTaskTitle,
|
|
1214
|
+
taskOwner,
|
|
1215
|
+
setTaskOwner,
|
|
1216
|
+
addRoomTask: () =>
|
|
1217
|
+
act('添加任务', async () => {
|
|
1218
|
+
if (taskTitle.trim() === '') throw new Error('任务标题不能为空');
|
|
1219
|
+
await postJson('/device-fleet/room/task', { roomId, action: 'add', title: taskTitle, owner: taskOwner });
|
|
1220
|
+
setTaskTitle('');
|
|
1221
|
+
}),
|
|
1222
|
+
updateRoomTask: (id, nextStatus) => act('更新任务', () => postJson('/device-fleet/room/task', { roomId, action: 'update', id, status: nextStatus })),
|
|
1223
|
+
closeCurrentRoom: () => act('关闭房间', () => postJson('/device-fleet/room/close', { roomId })),
|
|
1224
|
+
removeRoomMember: (memberId) => act('移除成员', () => postJson('/device-fleet/room/member/remove', { roomId, memberId })),
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
return h('div', null,
|
|
1228
|
+
status ? h('div', { style: { fontSize: 11, opacity: 0.6, marginBottom: 6 } }, status) : null,
|
|
1229
|
+
renderRoomDetail(detail, ctl),
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
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'),
|
|
1358
|
+
),
|
|
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 }));
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1145
1424
|
return {
|
|
1146
1425
|
inject: ['slots'],
|
|
1147
1426
|
apply(ctx) {
|
|
@@ -1158,11 +1437,14 @@ window.__ModuleLoader__.load({
|
|
|
1158
1437
|
ctx.slots.register({ name: 'sidebar.panellist', id: 'fleet', order: 20, label: '设备联邦' }, FleetIcon),
|
|
1159
1438
|
);
|
|
1160
1439
|
ctx.slots.register({ name: 'main', key: 'fleet' }, FleetConsole);
|
|
1161
|
-
// Session-native Room: member strip below the composer
|
|
1162
|
-
//
|
|
1440
|
+
// Session-native Room: member strip below the composer, plus a "房间"
|
|
1441
|
+
// Conversation View for the Room transcript/board.
|
|
1163
1442
|
ctx.slots.inject('conversation.composer.dock', () =>
|
|
1164
1443
|
ctx.slots.register({ name: 'conversation.composer.dock', id: 'fleet-room', order: 10 }, RoomDock),
|
|
1165
1444
|
);
|
|
1445
|
+
ctx.slots.inject('conversation.view', () =>
|
|
1446
|
+
ctx.slots.register({ name: 'conversation.view', id: 'room', order: 20, label: '房间' }, RoomView),
|
|
1447
|
+
);
|
|
1166
1448
|
// Configuration page: Settings → Plugins → Device Fleet (row + bundle).
|
|
1167
1449
|
ctx.slots.inject('plugins.row.config', () =>
|
|
1168
1450
|
ctx.slots.register({ name: 'plugins.row.config', key: 'dsh-device-fleet#device-fleet' }, FleetConfig),
|
|
@@ -114,4 +114,13 @@ dsh plugin --profile desktop add dsh-device-fleet@dev
|
|
|
114
114
|
- Room 恢复为「就是普通 Session」的体验:会话输入框下方新增 Room 成员条(composer.dock),显示 Room 成员;普通会话显示「升级为 Room」按钮(Host API `/room/upgrade`,无需工具调用);
|
|
115
115
|
- Host 监听 `session/event` 的 `user/message`:Room Host 会话里用户正常聊天输入会自动投递给房间参与者并唤醒它们(带 `[Room]` 前缀的注入消息不重播,避免循环);
|
|
116
116
|
- 参与者回复投递回 Host 时以 `[Room] <成员>: <内容>` 形式进入正常 Chat 流,不再需要独立的房间仪表盘;
|
|
117
|
-
- Fleet 控制台保留为设备/配对/审批/配置管理入口。
|
|
117
|
+
- Fleet 控制台保留为设备/配对/审批/配置管理入口。
|
|
118
|
+
## 0.1.0-dev.21(Room 回复地址修复)
|
|
119
|
+
|
|
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.
|
|
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",
|
package/src/host/room-service.js
CHANGED
|
@@ -247,6 +247,9 @@ export class RoomService {
|
|
|
247
247
|
if (!member || typeof this.createUserMessage !== 'function') return false
|
|
248
248
|
if (!(await this.ensureAgent(member))) return false
|
|
249
249
|
if (typeof member.agent?.followup !== 'function') return false
|
|
250
|
+
// Participant replies are rendered by the Room view; do not inject them into
|
|
251
|
+
// the Host's normal Chat stream (that caused the host agent to relay them).
|
|
252
|
+
if (member.role === 'host' && message.from.kind !== 'user') return false
|
|
250
253
|
const senderLabel = message.from.kind === 'user' ? 'user' : (message.from.name ?? 'agent')
|
|
251
254
|
// A user sender has no member id; the reply target is always the Host member.
|
|
252
255
|
const replyTarget = message.from.memberId ?? hostMemberId(room.hostSessionId)
|
package/src/plugin/index.js
CHANGED
|
@@ -304,7 +304,9 @@ export function apply(ctx, profileConfig) {
|
|
|
304
304
|
async sendRoomMessage({ roomId, to, text } = {}) {
|
|
305
305
|
if (typeof roomId !== 'string' || roomId === '') throw Object.assign(new Error('roomId is required'), { code: 'bad-request' })
|
|
306
306
|
const memberIds = rooms.status(roomId).members.map((member) => member.memberId)
|
|
307
|
-
const
|
|
307
|
+
const hostMemberId = `host:${roomId}`
|
|
308
|
+
const defaultRecipients = memberIds.filter((id) => id !== hostMemberId)
|
|
309
|
+
const recipients = Array.isArray(to) && to.length > 0 ? to : defaultRecipients.length > 0 ? defaultRecipients : [hostMemberId]
|
|
308
310
|
const result = await rooms.send({ roomId, from: { kind: 'user' }, to: recipients, text })
|
|
309
311
|
return { roomId, ...result }
|
|
310
312
|
},
|
|
@@ -424,37 +426,6 @@ export function apply(ctx, profileConfig) {
|
|
|
424
426
|
return ctx.on('approval/request', handler)
|
|
425
427
|
})
|
|
426
428
|
|
|
427
|
-
// A Room is a normal Session whose user input is also delivered to its
|
|
428
|
-
// participants. Typing in the Host conversation therefore needs no separate
|
|
429
|
-
// Room composer.
|
|
430
|
-
ctx.effect(() =>
|
|
431
|
-
ctx.on?.('session/event', (session, event) => {
|
|
432
|
-
try {
|
|
433
|
-
if (!session?.id || event?.type !== 'user/message') return
|
|
434
|
-
const room = rooms.rooms.get(session.id)
|
|
435
|
-
if (!room) return
|
|
436
|
-
const message = event.data
|
|
437
|
-
if (message?.source?.kind && message.source.kind !== 'user') return
|
|
438
|
-
const text = (Array.isArray(message?.content) ? message.content : [])
|
|
439
|
-
.filter((block) => block?.type === 'text')
|
|
440
|
-
.map((block) => block.text)
|
|
441
|
-
.join('\n')
|
|
442
|
-
.trim()
|
|
443
|
-
if (text === '' || text.startsWith('[Room]') || text.startsWith('[Fleet Room')) return
|
|
444
|
-
const recipients = rooms
|
|
445
|
-
.status(session.id)
|
|
446
|
-
.members.filter((member) => member.role !== 'host')
|
|
447
|
-
.map((member) => member.memberId)
|
|
448
|
-
if (recipients.length === 0) return
|
|
449
|
-
void rooms.send({ roomId: session.id, from: { kind: 'user' }, to: recipients, text }).catch((error) => {
|
|
450
|
-
logger.warn?.(`[device-fleet] cannot broadcast room message: ${String(error?.message ?? error)}`)
|
|
451
|
-
})
|
|
452
|
-
} catch (error) {
|
|
453
|
-
logger.warn?.(`[device-fleet] room broadcast failed: ${String(error?.message ?? error)}`)
|
|
454
|
-
}
|
|
455
|
-
}),
|
|
456
|
-
)
|
|
457
|
-
|
|
458
429
|
node.onRequest(async (request) => {
|
|
459
430
|
if (!host) throw Object.assign(new Error('this device cannot serve direct calls (no execution slot)'), { code: 'unsupported' })
|
|
460
431
|
return host.handleRequest(request)
|