dsh-device-fleet 0.1.0-dev.28 → 0.1.0-dev.29
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 +98 -6
- package/docs/notes/dev-release.md +7 -1
- package/package.json +1 -1
- package/src/plugin/index.js +29 -0
package/client.js
CHANGED
|
@@ -1149,6 +1149,49 @@ window.__ModuleLoader__.load({
|
|
|
1149
1149
|
|
|
1150
1150
|
// Room is a normal Session with participants. This dock keeps the member
|
|
1151
1151
|
// strip inside the conversation instead of a separate dashboard.
|
|
1152
|
+
function extractDraftText(input, depth = 0) {
|
|
1153
|
+
if (input === null || input === undefined || depth > 3) return '';
|
|
1154
|
+
if (typeof input === 'string') return input;
|
|
1155
|
+
if (typeof input !== 'object') return '';
|
|
1156
|
+
for (const key of ['draft', 'text', 'value', 'plainText', 'input', 'message', 'content', 'current']) {
|
|
1157
|
+
const value = input[key];
|
|
1158
|
+
if (typeof value === 'string' && value !== '') return value;
|
|
1159
|
+
if (value && typeof value === 'object') {
|
|
1160
|
+
const nested = extractDraftText(value, depth + 1);
|
|
1161
|
+
if (nested !== '') return nested;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return '';
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function insertMentionText(props, name) {
|
|
1168
|
+
const actions = props?.inputActions;
|
|
1169
|
+
if (actions && typeof actions === 'object') {
|
|
1170
|
+
for (const method of ['insertMention', 'insertText', 'appendText', 'replaceText', 'setText', 'setDraft']) {
|
|
1171
|
+
const fn = actions[method];
|
|
1172
|
+
if (typeof fn === 'function') {
|
|
1173
|
+
try {
|
|
1174
|
+
fn.call(actions, '@' + name + ' ');
|
|
1175
|
+
return true;
|
|
1176
|
+
} catch {
|
|
1177
|
+
/* try the next action */
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
try {
|
|
1183
|
+
const element = document.activeElement;
|
|
1184
|
+
if (element && (element.isContentEditable || element.tagName === 'TEXTAREA' || element.tagName === 'INPUT')) {
|
|
1185
|
+
element.focus();
|
|
1186
|
+
document.execCommand('insertText', false, '@' + name + ' ');
|
|
1187
|
+
return true;
|
|
1188
|
+
}
|
|
1189
|
+
} catch {
|
|
1190
|
+
/* fall through */
|
|
1191
|
+
}
|
|
1192
|
+
return false;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1152
1195
|
function RoomDock(props) {
|
|
1153
1196
|
const sessionId = props?.sessionId;
|
|
1154
1197
|
const [room, setRoom] = useState(null);
|
|
@@ -1223,12 +1266,61 @@ window.__ModuleLoader__.load({
|
|
|
1223
1266
|
}
|
|
1224
1267
|
if (!room) return null;
|
|
1225
1268
|
const names = room.members.filter((member) => member.role !== 'host').map((member) => member.name);
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1269
|
+
const inputState = (() => {
|
|
1270
|
+
try {
|
|
1271
|
+
return typeof props?.useInput === 'function' ? props.useInput() : null;
|
|
1272
|
+
} catch {
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
})();
|
|
1276
|
+
const draft = extractDraftText(inputState);
|
|
1277
|
+
const mention = /(?:^|\s)@([^\s@]*)$/.exec(draft);
|
|
1278
|
+
const candidates = mention
|
|
1279
|
+
? room.members.filter((member) => String(member.name ?? '').toLowerCase().includes(mention[1].toLowerCase()))
|
|
1280
|
+
: [];
|
|
1281
|
+
const completion =
|
|
1282
|
+
mention && candidates.length > 0
|
|
1283
|
+
? h('div', {
|
|
1284
|
+
style: {
|
|
1285
|
+
position: 'fixed',
|
|
1286
|
+
bottom: 96,
|
|
1287
|
+
left: '50%',
|
|
1288
|
+
transform: 'translateX(-50%)',
|
|
1289
|
+
width: 'min(520px, 80vw)',
|
|
1290
|
+
zIndex: 1000,
|
|
1291
|
+
background: 'var(--dsw-alias-bg-overlay, #1f1f1f)',
|
|
1292
|
+
color: 'var(--dsw-alias-label-primary, #e8e8e8)',
|
|
1293
|
+
border: '1px solid var(--dsw-alias-border-l2, #3a3a3a)',
|
|
1294
|
+
borderRadius: 10,
|
|
1295
|
+
boxShadow: '0 12px 32px rgba(0,0,0,.4)',
|
|
1296
|
+
padding: 6,
|
|
1297
|
+
font: '12px system-ui, sans-serif',
|
|
1298
|
+
},
|
|
1299
|
+
},
|
|
1300
|
+
h('div', { style: { opacity: 0.55, fontSize: 10, padding: '3px 7px 5px' } }, '可发送给:所有人 / 指定 agent'),
|
|
1301
|
+
...candidates.map((member) =>
|
|
1302
|
+
h('button', {
|
|
1303
|
+
key: member.memberId,
|
|
1304
|
+
type: 'button',
|
|
1305
|
+
onClick: () => {
|
|
1306
|
+
if (!insertMentionText(props, member.name)) {
|
|
1307
|
+
setStatus('请在输入框手动补全 @' + member.name)
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
style: { display: 'flex', alignItems: 'center', gap: 8, width: '100%', padding: '6px 8px', background: 'transparent', border: 'none', color: 'inherit', cursor: 'pointer', fontSize: 12, textAlign: 'left', borderRadius: 6 },
|
|
1311
|
+
}, avatarImg('/device-fleet/avatar/' + (member.role === 'host' ? 'host' : 'worker'), member.name), member.name + (member.role === 'host' ? '(host)' : '')),
|
|
1312
|
+
),
|
|
1313
|
+
)
|
|
1314
|
+
: null;
|
|
1315
|
+
return h('div', null,
|
|
1316
|
+
completion,
|
|
1317
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, fontSize: 11, opacity: 0.8, padding: '2px 2px 0', flexWrap: 'wrap' } },
|
|
1318
|
+
pill('Room', 'ok'),
|
|
1319
|
+
h('span', null, room.members.length + ' 名成员'),
|
|
1320
|
+
names.length > 0 ? h('span', null, '· ' + names.join(', ')) : h('span', null, '· 仅 host'),
|
|
1321
|
+
h('span', { style: { flex: 1 } }),
|
|
1322
|
+
h('span', { style: { opacity: 0.55 } }, '输入 @ 点名,不点名发给所有人'),
|
|
1323
|
+
),
|
|
1232
1324
|
);
|
|
1233
1325
|
}
|
|
1234
1326
|
|
|
@@ -147,4 +147,10 @@ dsh plugin --profile desktop add dsh-device-fleet@dev
|
|
|
147
147
|
## 0.1.0-dev.27(修复插件配置页空白)
|
|
148
148
|
|
|
149
149
|
- 根因:dev.26 头像库区块误用了不存在的 `sectionCard`,FleetConfig 渲染抛 ReferenceError,导致 Settings → Plugins → Device Fleet 配置页消失。
|
|
150
|
-
- 修复:头像库改用普通卡片容器;client smoke 增加配置页真实渲染用例(plugins.row.config)防止回归。
|
|
150
|
+
- 修复:头像库改用普通卡片容器;client smoke 增加配置页真实渲染用例(plugins.row.config)防止回归。
|
|
151
|
+
## 0.1.0-dev.28(Room 复用 DSH 输入框 + @点名解析)
|
|
152
|
+
|
|
153
|
+
- 移除房间页自带发送栏,房间消息直接使用 DSH 底部输入框;
|
|
154
|
+
- Host 监听 Room Host 会话的 user/message:解析 `@成员名`,被点名成员才加入收件人;未点名默认发给所有参与者;
|
|
155
|
+
- 消息文本会去掉 @mention 标记后写入房间日志;host 本身已收到 Chat 输入,不重复唤醒;
|
|
156
|
+
- 房间页保留成员/气泡/看板,底部提示用 @ 点名。
|
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.29",
|
|
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/plugin/index.js
CHANGED
|
@@ -479,6 +479,35 @@ export function apply(ctx, profileConfig) {
|
|
|
479
479
|
}),
|
|
480
480
|
)
|
|
481
481
|
|
|
482
|
+
// The host agent's own Chat replies are mirrored into the Room transcript as
|
|
483
|
+
// host speech (read-only for participants: recorded, never re-woken).
|
|
484
|
+
ctx.effect(() =>
|
|
485
|
+
ctx.on?.('session/event', (session, event) => {
|
|
486
|
+
try {
|
|
487
|
+
if (!session?.id || event?.type !== 'assistant/message') return
|
|
488
|
+
const room = rooms.rooms.get(session.id)
|
|
489
|
+
if (!room) return
|
|
490
|
+
const message = event.data
|
|
491
|
+
const text = (Array.isArray(message?.content) ? message.content : [])
|
|
492
|
+
.filter((block) => block?.type === 'text')
|
|
493
|
+
.map((block) => block.text)
|
|
494
|
+
.join('\n')
|
|
495
|
+
.trim()
|
|
496
|
+
if (text === '') return
|
|
497
|
+
const hostMemberId = `host:${session.id}`
|
|
498
|
+
void rooms.send({
|
|
499
|
+
roomId: session.id,
|
|
500
|
+
from: { sessionId: session.id },
|
|
501
|
+
to: [hostMemberId],
|
|
502
|
+
text,
|
|
503
|
+
skipWakeFor: [hostMemberId],
|
|
504
|
+
}).catch(() => {})
|
|
505
|
+
} catch {
|
|
506
|
+
/* ignore */
|
|
507
|
+
}
|
|
508
|
+
}),
|
|
509
|
+
)
|
|
510
|
+
|
|
482
511
|
node.onRequest(async (request) => {
|
|
483
512
|
if (!host) throw Object.assign(new Error('this device cannot serve direct calls (no execution slot)'), { code: 'unsupported' })
|
|
484
513
|
return host.handleRequest(request)
|