dsh-device-fleet 0.1.0-dev.29 → 0.1.0-dev.30

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
@@ -1317,9 +1317,21 @@ window.__ModuleLoader__.load({
1317
1317
  h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, opacity: 0.8, padding: '2px 2px 0', flexWrap: 'wrap' } },
1318
1318
  pill('Room', 'ok'),
1319
1319
  h('span', null, room.members.length + ' 名成员'),
1320
- names.length > 0 ? h('span', null, '· ' + names.join(', ')) : h('span', null, '· 仅 host'),
1320
+ h('span', { style: { opacity: 0.6 } }, '可发送给'),
1321
+ ...room.members.map((member) =>
1322
+ h('button', {
1323
+ key: member.memberId,
1324
+ type: 'button',
1325
+ onClick: () => {
1326
+ if (!insertMentionText(props, member.name)) {
1327
+ setStatus('请在输入框手动输入 @' + member.name)
1328
+ }
1329
+ },
1330
+ style: { padding: '1px 7px', borderRadius: 999, border: '1px solid var(--dsw-alias-border-l1, #333)', background: 'transparent', color: 'inherit', fontSize: 11, cursor: 'pointer' },
1331
+ }, '@' + member.name),
1332
+ ),
1321
1333
  h('span', { style: { flex: 1 } }),
1322
- h('span', { style: { opacity: 0.55 } }, '输入 @ 点名,不点名发给所有人'),
1334
+ h('span', { style: { opacity: 0.5, fontSize: 10 } }, '点名字插入 @;不点名发给所有人'),
1323
1335
  ),
1324
1336
  );
1325
1337
  }
@@ -1361,6 +1373,21 @@ window.__ModuleLoader__.load({
1361
1373
  return () => clearInterval(timer);
1362
1374
  }, [roomId]);
1363
1375
 
1376
+ // Only route Chat input into the Room while this view is open.
1377
+ useEffect(() => {
1378
+ if (!roomId) return undefined;
1379
+ const body = JSON.stringify({ roomId, focused: true });
1380
+ void fetch('/device-fleet/room/focus', { method: 'POST', headers: { 'content-type': 'application/json' }, body }).catch(() => {})
1381
+ return () => {
1382
+ void fetch('/device-fleet/room/focus', {
1383
+ method: 'POST',
1384
+ headers: { 'content-type': 'application/json' },
1385
+ body: JSON.stringify({ roomId, focused: false }),
1386
+ keepalive: true,
1387
+ }).catch(() => {})
1388
+ }
1389
+ }, [roomId]);
1390
+
1364
1391
  const act = async (label, action) => {
1365
1392
  setStatus(label + '…');
1366
1393
  try {
@@ -153,4 +153,8 @@ dsh plugin --profile desktop add dsh-device-fleet@dev
153
153
  - 移除房间页自带发送栏,房间消息直接使用 DSH 底部输入框;
154
154
  - Host 监听 Room Host 会话的 user/message:解析 `@成员名`,被点名成员才加入收件人;未点名默认发给所有参与者;
155
155
  - 消息文本会去掉 @mention 标记后写入房间日志;host 本身已收到 Chat 输入,不重复唤醒;
156
- - 房间页保留成员/气泡/看板,底部提示用 @ 点名。
156
+ - 房间页保留成员/气泡/看板,底部提示用 @ 点名。
157
+ ## 0.1.0-dev.29(@补全面板 + host 房间发言)
158
+
159
+ - 房间 Dock 读取 DSH 输入框草稿(标准 `useInput` 钩子),输入 `@` 时在输入框上方弹出补全面板,顶部标注「可发送给:所有人 / 指定 agent」,列出 Room 成员并可点击插入 `@name`;
160
+ - host agent 在 Chat 的 assistant 消息会同步写入 Room 日志(只读记录,不唤醒参与者),房间页左侧出现 host 气泡与 host 头像。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-device-fleet",
3
- "version": "0.1.0-dev.29",
3
+ "version": "0.1.0-dev.30",
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",
@@ -111,6 +111,12 @@ export function registerUiRoutes({ webServer, inbox, rooms, getOverview, actions
111
111
  return { ok: true, room: await actions.upgradeRoom(body ?? {}) }
112
112
  }),
113
113
  )
114
+ disposers.push(
115
+ actionRoute(webServer, '/device-fleet/room/focus', 'POST', async (body) => {
116
+ if (!actions?.setRoomFocus) throw Object.assign(new Error('room actions are unavailable'), { code: 'unsupported' })
117
+ return { ok: true, result: actions.setRoomFocus(body ?? {}) }
118
+ }),
119
+ )
114
120
  disposers.push(
115
121
  actionRoute(webServer, '/device-fleet/pair/invite', 'POST', async () => {
116
122
  if (!actions?.createInvite) throw Object.assign(new Error('pairing actions are unavailable'), { code: 'unsupported' })
@@ -227,6 +227,9 @@ export function apply(ctx, profileConfig) {
227
227
  createUserMessage,
228
228
  stateFile: join(state.dataDir, 'rooms.json'),
229
229
  })
230
+ // User input is routed to a Room only while its Conversation View is open;
231
+ // normal Chat with the host agent must not wake participants.
232
+ const focusedRooms = new Set()
230
233
 
231
234
  // Client approval panel data plane (dev.9). Optional service: host-only
232
235
  // deployments simply do not expose the panel routes.
@@ -330,6 +333,12 @@ export function apply(ctx, profileConfig) {
330
333
  throw Object.assign(new Error('action must be add or update'), { code: 'bad-request' })
331
334
  },
332
335
  closeRoom: ({ roomId, reason } = {}) => rooms.close({ roomId, reason: reason ?? 'closed by user' }),
336
+ setRoomFocus: ({ roomId, focused } = {}) => {
337
+ if (typeof roomId !== 'string' || roomId === '') throw Object.assign(new Error('roomId is required'), { code: 'bad-request' })
338
+ if (focused === true) focusedRooms.add(roomId)
339
+ else focusedRooms.delete(roomId)
340
+ return { roomId, focused: focused === true }
341
+ },
333
342
  removeMember: ({ roomId, memberId, reason } = {}) => rooms.removeMember({ roomId, memberId, reason: reason ?? 'removed by user' }),
334
343
  async upgradeRoom({ sessionId } = {}) {
335
344
  if (typeof sessionId !== 'string' || sessionId === '') throw Object.assign(new Error('sessionId is required'), { code: 'bad-request' })
@@ -445,6 +454,7 @@ export function apply(ctx, profileConfig) {
445
454
  if (!session?.id || event?.type !== 'user/message') return
446
455
  const room = rooms.rooms.get(session.id)
447
456
  if (!room) return
457
+ if (!focusedRooms.has(session.id)) return
448
458
  const message = event.data
449
459
  if (message?.source?.kind && message.source.kind !== 'user') return
450
460
  const text = (Array.isArray(message?.content) ? message.content : [])