dsh-device-fleet 0.1.0-dev.21 → 0.1.0-dev.22
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 +106 -5
- package/docs/notes/dev-release.md +4 -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,7 +1140,103 @@ 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 } }, '房间消息在「房间」页交流'),
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
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 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 } }, '多设备成员共享的会话'),
|
|
1238
|
+
),
|
|
1239
|
+
h(RoomPane, { roomId: sessionId }),
|
|
1142
1240
|
);
|
|
1143
1241
|
}
|
|
1144
1242
|
|
|
@@ -1158,11 +1256,14 @@ window.__ModuleLoader__.load({
|
|
|
1158
1256
|
ctx.slots.register({ name: 'sidebar.panellist', id: 'fleet', order: 20, label: '设备联邦' }, FleetIcon),
|
|
1159
1257
|
);
|
|
1160
1258
|
ctx.slots.register({ name: 'main', key: 'fleet' }, FleetConsole);
|
|
1161
|
-
// Session-native Room: member strip below the composer
|
|
1162
|
-
//
|
|
1259
|
+
// Session-native Room: member strip below the composer, plus a "房间"
|
|
1260
|
+
// Conversation View for the Room transcript/board.
|
|
1163
1261
|
ctx.slots.inject('conversation.composer.dock', () =>
|
|
1164
1262
|
ctx.slots.register({ name: 'conversation.composer.dock', id: 'fleet-room', order: 10 }, RoomDock),
|
|
1165
1263
|
);
|
|
1264
|
+
ctx.slots.inject('conversation.view', () =>
|
|
1265
|
+
ctx.slots.register({ name: 'conversation.view', id: 'room', order: 20, label: '房间' }, RoomView),
|
|
1266
|
+
);
|
|
1166
1267
|
// Configuration page: Settings → Plugins → Device Fleet (row + bundle).
|
|
1167
1268
|
ctx.slots.inject('plugins.row.config', () =>
|
|
1168
1269
|
ctx.slots.register({ name: 'plugins.row.config', key: 'dsh-device-fleet#device-fleet' }, FleetConfig),
|
|
@@ -114,4 +114,7 @@ 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 回执发给自己/失败的问题。
|
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.22",
|
|
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)
|