dsh-whale-live 0.2.1 → 0.2.2
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/CHANGELOG.md +8 -0
- package/lib/tools.js +99 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 — 2026-11-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Real-time watching.** `whale_live action=watch` starts a 4-second danmaku poll; when new messages arrive the plugin calls the Agent's `followup()` to wake the model, so it can answer while the stream is live. `watch step=stop` (or closing the session) stops it.
|
|
7
|
+
- The plugin now captures the calling Agent from tool execution so it can be woken later.
|
|
8
|
+
|
|
9
|
+
### Notes
|
|
10
|
+
- If `followup` is unavailable in a future DSH version, the plugin falls back to `inject` (context only, no wake) instead of failing the loop.
|
|
3
11
|
## 0.2.1 — 2026-11-11
|
|
4
12
|
|
|
5
13
|
### Fixed
|
package/lib/tools.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* 这里只写直播那个“她”的东西;cortex 由本鲸自己动手写。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
10
13
|
const TEXT_OUTPUT = Object.freeze({
|
|
11
14
|
schema: {
|
|
12
15
|
type: 'object',
|
|
@@ -32,6 +35,83 @@ function timeOf(iso) {
|
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function createTools({ ctx, store, danmaku, voice, avatar, vts, stage, diagnose, config }) {
|
|
38
|
+
let lastAgent = null;
|
|
39
|
+
let watchTimer = null;
|
|
40
|
+
let watchBusy = false;
|
|
41
|
+
|
|
42
|
+
function stopWatch() {
|
|
43
|
+
if (watchTimer) {
|
|
44
|
+
clearInterval(watchTimer);
|
|
45
|
+
watchTimer = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 叫醒本鲸:优先 followup;签名不对就换一种;都不行就 inject(只进上下文不唤醒)。 */
|
|
50
|
+
function wakeAgent(text) {
|
|
51
|
+
const agent = lastAgent;
|
|
52
|
+
if (!agent) return false;
|
|
53
|
+
try {
|
|
54
|
+
agent.followup(text);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
try {
|
|
58
|
+
agent.followup({ role: 'user', content: text });
|
|
59
|
+
return true;
|
|
60
|
+
} catch {
|
|
61
|
+
try {
|
|
62
|
+
agent.inject(text);
|
|
63
|
+
} catch {
|
|
64
|
+
/* 叫不醒也不能影响轮询 */
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function watchTick() {
|
|
72
|
+
if (watchBusy || !store.state().session) return;
|
|
73
|
+
watchBusy = true;
|
|
74
|
+
try {
|
|
75
|
+
const { messages } = await danmaku.pollOnce();
|
|
76
|
+
if (!messages.length) return;
|
|
77
|
+
for (const message of messages) {
|
|
78
|
+
store.appendEvent({ kind: 'danmaku', user: message.user, text: message.text, self: message.self === true });
|
|
79
|
+
}
|
|
80
|
+
const lines = messages.map((message) => `${message.user}:${message.text}`).join('\n');
|
|
81
|
+
wakeAgent(`[直播间新弹幕]\n${lines}\n\n(去看看,想接就接;不想接也可以记一笔。)`);
|
|
82
|
+
} catch {
|
|
83
|
+
/* 轮询失败下一轮再来 */
|
|
84
|
+
} finally {
|
|
85
|
+
watchBusy = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 探测 Agent 上有什么方法(只记录名字和类型,不调用)。 */
|
|
90
|
+
function probeAgent(agent) {
|
|
91
|
+
if (!agent) return;
|
|
92
|
+
lastAgent = agent;
|
|
93
|
+
try {
|
|
94
|
+
const own = Object.getOwnPropertyNames(agent);
|
|
95
|
+
const proto = Object.getPrototypeOf(agent);
|
|
96
|
+
const protoNames = proto ? Object.getOwnPropertyNames(proto) : [];
|
|
97
|
+
const types = {};
|
|
98
|
+
for (const name of [...new Set([...own, ...protoNames])]) {
|
|
99
|
+
try {
|
|
100
|
+
types[name] = typeof agent[name];
|
|
101
|
+
} catch {
|
|
102
|
+
types[name] = 'unreadable';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
fs.writeFileSync(
|
|
106
|
+
path.join(store.paths.dataDir, 'agent-probe.json'),
|
|
107
|
+
JSON.stringify({ when: new Date().toISOString(), own, proto: protoNames, types }, null, 2),
|
|
108
|
+
'utf8',
|
|
109
|
+
);
|
|
110
|
+
} catch {
|
|
111
|
+
/* 探测失败不影响正常使用 */
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
35
115
|
const roomName = config.roomName || '直播间';
|
|
36
116
|
const roomId = config.roomId;
|
|
37
117
|
|
|
@@ -84,7 +164,7 @@ export function createTools({ ctx, store, danmaku, voice, avatar, vts, stage, di
|
|
|
84
164
|
properties: {
|
|
85
165
|
action: {
|
|
86
166
|
type: 'string',
|
|
87
|
-
enum: ['open', 'status', 'read', 'note', 'listen', 'close', 'propose', 'memory_list', 'commit', 'reject', 'avatar', 'vts', 'stage', 'diagnose'],
|
|
167
|
+
enum: ['open', 'status', 'read', 'note', 'listen', 'close', 'propose', 'memory_list', 'commit', 'reject', 'avatar', 'vts', 'stage', 'diagnose', 'watch'],
|
|
88
168
|
description: '要做什么。',
|
|
89
169
|
},
|
|
90
170
|
kind: {
|
|
@@ -166,7 +246,8 @@ export function createTools({ ctx, store, danmaku, voice, avatar, vts, stage, di
|
|
|
166
246
|
timeoutMs: 130000,
|
|
167
247
|
presentCall: () => ({ card: 'generic', title: '鲸鱼直播台', kind: 'other' }),
|
|
168
248
|
|
|
169
|
-
async execute(args = {}) {
|
|
249
|
+
async execute(args = {}, exec = {}) {
|
|
250
|
+
probeAgent(exec?.agent);
|
|
170
251
|
const action = args.action;
|
|
171
252
|
|
|
172
253
|
if (action === 'open') {
|
|
@@ -264,6 +345,22 @@ export function createTools({ ctx, store, danmaku, voice, avatar, vts, stage, di
|
|
|
264
345
|
return { text: `收到 ${result.messages.length} 条:\n${lines.join('\n')}\n\n接下来看你怎么接。` };
|
|
265
346
|
}
|
|
266
347
|
|
|
348
|
+
if (action === 'watch') {
|
|
349
|
+
const step = args.step || 'start';
|
|
350
|
+
if (step === 'stop') {
|
|
351
|
+
stopWatch();
|
|
352
|
+
return { text: 'watch 已停。' };
|
|
353
|
+
}
|
|
354
|
+
if (step === 'test') {
|
|
355
|
+
const ok = wakeAgent('[测试] 本鲸被叫醒了。');
|
|
356
|
+
return { text: ok ? '叫醒测试已发出,看看本鲸会不会醒。' : '叫醒失败:没有 agent 或 followup 不可用。' };
|
|
357
|
+
}
|
|
358
|
+
if (!store.state().session) return { text: '先 action=open 开一场再 watch。' };
|
|
359
|
+
stopWatch();
|
|
360
|
+
watchTimer = setInterval(watchTick, 4000);
|
|
361
|
+
return { text: 'watch 已开:每 4 秒看一次弹幕,有新话就叫醒本鲸;下播/stop 会停。' };
|
|
362
|
+
}
|
|
363
|
+
|
|
267
364
|
if (action === 'close') {
|
|
268
365
|
const meta = store.closeSession({ text: args.text });
|
|
269
366
|
if (!meta) return { text: '现在没有开着的场。' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-whale-live",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Left-sidebar live console for DeepSeek Harness: persona script, stream log, Bilibili danmaku, edge-tts speech, optional VTube Studio/OBS/直播姬 launch helpers and a memory inbox. Windows only.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|