openclaw-glance-plugin 0.1.15 → 0.1.16
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/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- 与 `openclaw-bridge` 建立 WebSocket 长连接
|
|
24
24
|
- 支持请求:`watch.create` / `watch.activate` / `watch.pause` / `watch.delete` / `ticker.query` / `notify.send` / `ping`
|
|
25
25
|
- 支持渠道:`openclaw` / `email` / `call` / `sms` / `dingtalk`
|
|
26
|
+
- 建议在 `channel_configs.openclaw` 中携带路由字段(如 `channel`、`session_key`、`account_id`、`conversation_id`),便于触发后回推到正确会话
|
|
26
27
|
- 订阅推送:`watch.triggered`
|
|
27
28
|
- 主动发起通知结果推送:`notify.sent`
|
|
28
29
|
- 自动重连 + 心跳
|
|
@@ -127,6 +128,7 @@ await adapter.submitWatchDemand({
|
|
|
127
128
|
|
|
128
129
|
- `watch_query_ticker`
|
|
129
130
|
- `watch_create`
|
|
131
|
+
- `watch_list`
|
|
130
132
|
- `watch_pause`
|
|
131
133
|
- `watch_activate`
|
|
132
134
|
- `watch_remove`
|
package/package.json
CHANGED
|
@@ -30,6 +30,7 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
|
|
|
30
30
|
|
|
31
31
|
- `watch.query_ticker`
|
|
32
32
|
- `watch.create`
|
|
33
|
+
- `watch.list`
|
|
33
34
|
- `watch.pause`
|
|
34
35
|
- `watch.activate`
|
|
35
36
|
- `watch.remove`
|
|
@@ -43,7 +44,8 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
|
|
|
43
44
|
1. 用户是“查行情”意图:先调用 `watch.query_ticker`
|
|
44
45
|
2. 用户是“盯盘创建”意图:先补齐参数后调用 `watch.create`
|
|
45
46
|
3. 用户是“暂停/恢复/删除”意图:分别调用 `watch.pause` / `watch.activate` / `watch.remove`
|
|
46
|
-
4.
|
|
47
|
+
4. 用户是“查看策略/查看我的策略/看 active 或 paused 或 completed 策略”意图:调用 `watch.list`
|
|
48
|
+
5. 用户是“立即发短信/打电话/发邮件/发钉钉”意图:调用 `notify.sms` / `notify.call` / `notify.email` / `notify.dingtalk`
|
|
47
49
|
|
|
48
50
|
禁止跳步:创建盯盘前若缺关键字段必须先追问。
|
|
49
51
|
|
|
@@ -146,6 +148,24 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
|
|
|
146
148
|
失败处理:
|
|
147
149
|
- 返回失败原因并提示用户确认策略 ID
|
|
148
150
|
|
|
151
|
+
#### `watch.list`
|
|
152
|
+
|
|
153
|
+
参数(可选):
|
|
154
|
+
- `status`:策略状态过滤。可传 `active` / `paused` / `completed` / `failed` / `expired`;不传表示查询该用户全部策略
|
|
155
|
+
- `product_code`(或 `productCode`):按标的代码过滤
|
|
156
|
+
|
|
157
|
+
成功判定:
|
|
158
|
+
- 返回 `success = true`
|
|
159
|
+
- `data.total` 为命中策略数,`data.strategies` 为策略列表
|
|
160
|
+
|
|
161
|
+
失败处理:
|
|
162
|
+
- 返回失败原因,不要静默重试
|
|
163
|
+
- 若筛选条件为空结果,明确告知“当前条件下没有策略”
|
|
164
|
+
|
|
165
|
+
安全约束(必须):
|
|
166
|
+
- `watch.list` 只能查询当前连接用户自己的策略
|
|
167
|
+
- 不要尝试通过参数传 `user_id` / `use_id` 越权查询
|
|
168
|
+
|
|
149
169
|
#### `notify.sms` / `notify.call` / `notify.email` / `notify.dingtalk`
|
|
150
170
|
|
|
151
171
|
参数:
|
|
@@ -188,6 +208,11 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
|
|
|
188
208
|
回执说明:
|
|
189
209
|
- 直连通知发送完成后,客户端会收到 `notify.sent` 事件(`overall_status/success_count/failed_count/deliveries`)
|
|
190
210
|
|
|
211
|
+
离线补发识别(`watch.triggered`):
|
|
212
|
+
- 若事件包含 `delivery_mode = "offline_replay"` 或 `replayed = true`,表示这是用户离线期间触发后补发的消息
|
|
213
|
+
- `trigger_time` 表示原始触发时间,`replayed_at` 表示补发时间
|
|
214
|
+
- 向用户描述时应明确区分:例如“这条是离线期间触发,现已补发到当前会话”
|
|
215
|
+
|
|
191
216
|
## 调用判定规则
|
|
192
217
|
|
|
193
218
|
只有在用户明确表达以下意图时调用插件:
|
|
@@ -164,6 +164,10 @@ export class OpenClawBridgeClient extends EventEmitter {
|
|
|
164
164
|
return this._request('watch.pause', { strategy_id: strategyId });
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
async listWatches(payload = {}) {
|
|
168
|
+
return this._request('watch.list', payload || {});
|
|
169
|
+
}
|
|
170
|
+
|
|
167
171
|
async deleteWatch(strategyId) {
|
|
168
172
|
return this._request('watch.delete', { strategy_id: strategyId });
|
|
169
173
|
}
|
package/src/plugin/index.js
CHANGED
|
@@ -56,6 +56,52 @@ async function getReadyRuntime(startupPromise) {
|
|
|
56
56
|
return activeRuntime;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function pickFirstString(...values) {
|
|
60
|
+
for (const value of values) {
|
|
61
|
+
if (typeof value === 'string' && value.trim()) {
|
|
62
|
+
return value.trim();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function deriveOpenclawRouting({ params = {}, context = {} } = {}) {
|
|
69
|
+
const metadata = context?.event?.metadata || {};
|
|
70
|
+
|
|
71
|
+
const channel = pickFirstString(
|
|
72
|
+
params?.channel,
|
|
73
|
+
params?.source_channel,
|
|
74
|
+
metadata?.channel,
|
|
75
|
+
metadata?.channelId,
|
|
76
|
+
context?.channel,
|
|
77
|
+
context?.channelId
|
|
78
|
+
);
|
|
79
|
+
const accountId = pickFirstString(params?.account_id, context?.accountId, metadata?.accountId);
|
|
80
|
+
const sessionKey = pickFirstString(
|
|
81
|
+
params?.session_key,
|
|
82
|
+
params?.sessionKey,
|
|
83
|
+
context?.sessionKey,
|
|
84
|
+
metadata?.sessionKey
|
|
85
|
+
);
|
|
86
|
+
const conversationId = pickFirstString(
|
|
87
|
+
params?.conversation_id,
|
|
88
|
+
params?.conversationId,
|
|
89
|
+
params?.chat_id,
|
|
90
|
+
params?.chatId,
|
|
91
|
+
context?.conversationId,
|
|
92
|
+
metadata?.conversationId,
|
|
93
|
+
metadata?.chatId,
|
|
94
|
+
metadata?.groupId
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const routing = {};
|
|
98
|
+
if (channel) routing.channel = channel;
|
|
99
|
+
if (accountId) routing.account_id = accountId;
|
|
100
|
+
if (sessionKey) routing.session_key = sessionKey;
|
|
101
|
+
if (conversationId) routing.conversation_id = conversationId;
|
|
102
|
+
return routing;
|
|
103
|
+
}
|
|
104
|
+
|
|
59
105
|
function mapDemandToCreatePayload(demand = {}) {
|
|
60
106
|
const channels = Array.isArray(demand.channels)
|
|
61
107
|
? demand.channels
|
|
@@ -101,6 +147,33 @@ function mapDemandToCreatePayload(demand = {}) {
|
|
|
101
147
|
};
|
|
102
148
|
}
|
|
103
149
|
|
|
150
|
+
function mergeOpenclawChannelConfig(payload = {}, context = {}) {
|
|
151
|
+
const merged = { ...(payload || {}) };
|
|
152
|
+
const channelConfigs = { ...(merged.channel_configs || {}) };
|
|
153
|
+
const openclawConfig = { ...(channelConfigs.openclaw || {}) };
|
|
154
|
+
const routing = deriveOpenclawRouting({ params: merged, context });
|
|
155
|
+
|
|
156
|
+
if (Object.keys(routing).length === 0) {
|
|
157
|
+
return merged;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
channelConfigs.openclaw = {
|
|
161
|
+
...openclawConfig,
|
|
162
|
+
...routing
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const channels = Array.isArray(merged.channels)
|
|
166
|
+
? merged.channels
|
|
167
|
+
.filter((x) => typeof x === 'string' && x.trim())
|
|
168
|
+
.map((x) => x.trim().toLowerCase())
|
|
169
|
+
: [];
|
|
170
|
+
if (!channels.includes('openclaw')) channels.unshift('openclaw');
|
|
171
|
+
|
|
172
|
+
merged.channels = channels;
|
|
173
|
+
merged.channel_configs = channelConfigs;
|
|
174
|
+
return merged;
|
|
175
|
+
}
|
|
176
|
+
|
|
104
177
|
function buildControlApi(startupPromise) {
|
|
105
178
|
return {
|
|
106
179
|
async queryTickerData(query = {}) {
|
|
@@ -117,9 +190,10 @@ function buildControlApi(startupPromise) {
|
|
|
117
190
|
product_type: productType
|
|
118
191
|
});
|
|
119
192
|
},
|
|
120
|
-
async createWatch(payload = {}) {
|
|
193
|
+
async createWatch(payload = {}, context = {}) {
|
|
121
194
|
const runtime = await getReadyRuntime(startupPromise);
|
|
122
|
-
|
|
195
|
+
const normalized = mergeOpenclawChannelConfig(payload, context);
|
|
196
|
+
return runtime.request('watch.create', normalized);
|
|
123
197
|
},
|
|
124
198
|
async sendNotification(input = {}) {
|
|
125
199
|
const runtime = await getReadyRuntime(startupPromise);
|
|
@@ -142,14 +216,19 @@ function buildControlApi(startupPromise) {
|
|
|
142
216
|
async sendDingtalk(payload = {}) {
|
|
143
217
|
return this.sendNotification({ channel: 'dingtalk', payload });
|
|
144
218
|
},
|
|
145
|
-
async submitWatchDemand(demand = {}) {
|
|
219
|
+
async submitWatchDemand(demand = {}, context = {}) {
|
|
146
220
|
const runtime = await getReadyRuntime(startupPromise);
|
|
147
|
-
|
|
221
|
+
const payload = mapDemandToCreatePayload(demand);
|
|
222
|
+
return runtime.request('watch.create', mergeOpenclawChannelConfig(payload, context));
|
|
148
223
|
},
|
|
149
224
|
async pauseWatch(strategyId) {
|
|
150
225
|
const runtime = await getReadyRuntime(startupPromise);
|
|
151
226
|
return runtime.request('watch.pause', { strategy_id: strategyId });
|
|
152
227
|
},
|
|
228
|
+
async listWatches(payload = {}) {
|
|
229
|
+
const runtime = await getReadyRuntime(startupPromise);
|
|
230
|
+
return runtime.request('watch.list', payload || {});
|
|
231
|
+
},
|
|
153
232
|
async activateWatch(strategyId) {
|
|
154
233
|
const runtime = await getReadyRuntime(startupPromise);
|
|
155
234
|
return runtime.request('watch.activate', { strategy_id: strategyId });
|
|
@@ -176,7 +255,8 @@ function tryRegisterTool(registerTool, name, description, parameters, handler) {
|
|
|
176
255
|
parameters: schema,
|
|
177
256
|
inputSchema: schema,
|
|
178
257
|
handler,
|
|
179
|
-
execute: async (_toolCallId, params) =>
|
|
258
|
+
execute: async (_toolCallId, params, _onUpdate, context) =>
|
|
259
|
+
handler(params || {}, { context: context || {} })
|
|
180
260
|
};
|
|
181
261
|
const meta = {
|
|
182
262
|
name,
|
|
@@ -289,7 +369,7 @@ function registerControlTools(api, controlApi) {
|
|
|
289
369
|
channel_configs: { type: 'object' }
|
|
290
370
|
}
|
|
291
371
|
},
|
|
292
|
-
(args) => controlApi.createWatch(args || {})
|
|
372
|
+
(args, meta = {}) => controlApi.createWatch(args || {}, meta?.context || {})
|
|
293
373
|
);
|
|
294
374
|
|
|
295
375
|
const strategySchema = {
|
|
@@ -301,6 +381,22 @@ function registerControlTools(api, controlApi) {
|
|
|
301
381
|
}
|
|
302
382
|
};
|
|
303
383
|
|
|
384
|
+
tryRegisterTool(
|
|
385
|
+
registerTool,
|
|
386
|
+
'watch_list',
|
|
387
|
+
'List watch strategies for current user',
|
|
388
|
+
{
|
|
389
|
+
type: 'object',
|
|
390
|
+
additionalProperties: true,
|
|
391
|
+
properties: {
|
|
392
|
+
status: { type: 'string' },
|
|
393
|
+
product_code: { type: 'string' },
|
|
394
|
+
productCode: { type: 'string' }
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
(args) => controlApi.listWatches(args || {})
|
|
398
|
+
);
|
|
399
|
+
|
|
304
400
|
tryRegisterTool(
|
|
305
401
|
registerTool,
|
|
306
402
|
'watch_pause',
|
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
function pickFirstString(...values) {
|
|
2
|
+
for (const value of values) {
|
|
3
|
+
if (typeof value === 'string' && value.trim()) {
|
|
4
|
+
return value.trim();
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function extractRoutingFromTriggeredEvent(event) {
|
|
11
|
+
const payload = event?.payload || {};
|
|
12
|
+
const openclaw = payload?.channel_configs?.openclaw || payload?.openclaw || {};
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
channel: pickFirstString(
|
|
16
|
+
payload?.channel,
|
|
17
|
+
payload?.source_channel,
|
|
18
|
+
openclaw?.channel,
|
|
19
|
+
openclaw?.source_channel
|
|
20
|
+
),
|
|
21
|
+
accountId: pickFirstString(payload?.account_id, openclaw?.account_id),
|
|
22
|
+
sessionKey: pickFirstString(
|
|
23
|
+
payload?.session_key,
|
|
24
|
+
payload?.sessionKey,
|
|
25
|
+
openclaw?.session_key,
|
|
26
|
+
openclaw?.sessionKey
|
|
27
|
+
),
|
|
28
|
+
conversationId: pickFirstString(
|
|
29
|
+
payload?.conversation_id,
|
|
30
|
+
payload?.conversationId,
|
|
31
|
+
payload?.chat_id,
|
|
32
|
+
payload?.chatId,
|
|
33
|
+
openclaw?.conversation_id,
|
|
34
|
+
openclaw?.conversationId,
|
|
35
|
+
openclaw?.chat_id,
|
|
36
|
+
openclaw?.chatId
|
|
37
|
+
)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
1
41
|
export class PluginDispatcher {
|
|
2
42
|
constructor({ runtime }) {
|
|
3
43
|
this.runtime = runtime;
|
|
@@ -7,12 +47,22 @@ export class PluginDispatcher {
|
|
|
7
47
|
if (!this.runtime?.dispatchReply) {
|
|
8
48
|
return;
|
|
9
49
|
}
|
|
10
|
-
|
|
50
|
+
|
|
51
|
+
const routing = extractRoutingFromTriggeredEvent(event);
|
|
52
|
+
const dispatchPayload = {
|
|
11
53
|
text: event?.payload?.message || '',
|
|
12
54
|
metadata: {
|
|
13
55
|
source: 'watch.triggered',
|
|
14
|
-
event
|
|
56
|
+
event,
|
|
57
|
+
routing
|
|
15
58
|
}
|
|
16
|
-
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (routing.channel) dispatchPayload.channel = routing.channel;
|
|
62
|
+
if (routing.accountId) dispatchPayload.accountId = routing.accountId;
|
|
63
|
+
if (routing.sessionKey) dispatchPayload.sessionKey = routing.sessionKey;
|
|
64
|
+
if (routing.conversationId) dispatchPayload.conversationId = routing.conversationId;
|
|
65
|
+
|
|
66
|
+
await this.runtime.dispatchReply(dispatchPayload);
|
|
17
67
|
}
|
|
18
68
|
}
|