openclaw-glance-plugin 0.1.8 → 0.1.10

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
@@ -16,13 +16,15 @@
16
16
  - 用户修改状态:调用 `activate` / `pause`
17
17
  - 用户删除盯盘:调用 `remove`
18
18
  - 需要接收触发消息:由插件后台运行时持续接收
19
+ - 需要主动通过邮件、短信、电话发送通知时
19
20
 
20
21
  ## 功能
21
22
 
22
23
  - 与 `openclaw-bridge` 建立 WebSocket 长连接
23
- - 支持请求:`watch.create` / `watch.activate` / `watch.pause` / `watch.delete` / `ping`
24
- - 支持渠道:`openclaw` / `email` / `call`
24
+ - 支持请求:`watch.create` / `watch.activate` / `watch.pause` / `watch.delete` / `ticker.query` / `notify.send` / `ping`
25
+ - 支持渠道:`openclaw` / `email` / `call` / `sms` / `dingtalk`
25
26
  - 订阅推送:`watch.triggered`
27
+ - 主动发起通知结果推送:`notify.sent`
26
28
  - 自动重连 + 心跳
27
29
  - 严格单活(同 token 只允许一个活跃进程)
28
30
  - 断线请求排队(可配置),重连后自动冲刷
@@ -98,7 +100,7 @@ await adapter.submitWatchDemand({
98
100
  productType: 'hk_stock',
99
101
  condition: 'price >= threshold',
100
102
  variables: { threshold: 8.97 },
101
- channels: ['openclaw', 'email', 'call'], // openclaw 必传,email/call 可选
103
+ channels: ['openclaw', 'email', 'call', 'sms', 'dingtalk'], // openclaw 必传,其它可选
102
104
  emailConfig: {
103
105
  to_address: 'demo@example.com',
104
106
  template_id: 4
@@ -106,10 +108,35 @@ await adapter.submitWatchDemand({
106
108
  callConfig: {
107
109
  phone: '13800138000',
108
110
  customer_name: 'Demo'
111
+ },
112
+ smsConfig: {
113
+ receiver: '13800138000',
114
+ template_id: 90010,
115
+ content: '测试短信'
116
+ },
117
+ dingtalkConfig: {
118
+ cas_id: 'user.dingtalk',
119
+ template_id: 3,
120
+ msg_type: 'text',
121
+ content: '测试钉钉消息'
109
122
  }
110
123
  });
111
124
  ```
112
125
 
126
+ ## 插件工具(OpenClaw 调用)
127
+
128
+ - `watch_query_ticker`
129
+ - `watch_create`
130
+ - `watch_pause`
131
+ - `watch_activate`
132
+ - `watch_remove`
133
+ - `notify_sms`
134
+ - `notify_call`
135
+ - `notify_email`
136
+ - `notify_dingtalk`
137
+
138
+ `notify_*` 工具走 bridge `notify.send` 直连通知链路,发送完成后客户端会收到 `notify.sent` 回执事件。
139
+
113
140
  ## 客户端事件
114
141
 
115
142
  - `connected`
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "openclaw-glance-plugin",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "OpenClaw plugin client for ticker-monitor openclaw-bridge",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "openclaw": {
8
- "extensions": ["./index.js"]
8
+ "extensions": [
9
+ "./index.js"
10
+ ]
9
11
  },
10
12
  "exports": {
11
13
  ".": "./index.js",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: glance-watch
3
- description: 智能盯盘插件,用于监控A股、港股、比特币等金融市场行情并在条件触发时发送提醒。当用户要求盯盘、监控价格、设置提醒时自动触发,例如"帮我盯着比特币"、监控某只股票、涨跌幅提醒等。
3
+ description: 智能盯盘插件,用于监控A股、港股、比特币等金融市场行情并在条件触发时发送提醒。当用户要求盯盘、监控价格、设置提醒、需要通过邮件/电话/短信/钉钉发起通知时自动触发,例如"帮我盯着比特币"、监控某只股票、涨跌幅提醒、短信通知我等。
4
4
  ---
5
5
 
6
6
  # Glance Watch 智能盯盘
@@ -18,8 +18,8 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
18
18
  - `variables`: 变量值
19
19
 
20
20
  3. **通过已安装运行时提交盯盘请求**(长连接由宿主运行时维护)
21
-
22
21
  4. **用户要求“查行情/看当前价格/报价”时**,优先调用 `queryTickerData` 获取实时数据,再决定是否创建盯盘策略。
22
+ 5. **用户要求“发短信/打电话/发邮件”时**。
23
23
 
24
24
  ## 调用契约(必须遵循)
25
25
 
@@ -32,12 +32,17 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
32
32
  - `watch.pause`
33
33
  - `watch.activate`
34
34
  - `watch.remove`
35
+ - `notify.sms`
36
+ - `notify.call`
37
+ - `notify.email`
38
+ - `notify.dingtalk`
35
39
 
36
40
  ### 调用顺序
37
41
 
38
42
  1. 用户是“查行情”意图:先调用 `watch.query_ticker`
39
43
  2. 用户是“盯盘创建”意图:先补齐参数后调用 `watch.create`
40
44
  3. 用户是“暂停/恢复/删除”意图:分别调用 `watch.pause` / `watch.activate` / `watch.remove`
45
+ 4. 用户是“立即发短信/打电话/发邮件/发钉钉”意图:调用 `notify.sms` / `notify.call` / `notify.email` / `notify.dingtalk`
41
46
 
42
47
  禁止跳步:创建盯盘前若缺关键字段必须先追问。
43
48
 
@@ -69,6 +74,12 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
69
74
  - `channels`(默认至少包含 `openclaw`)
70
75
  - 对应渠道配置(`emailConfig/callConfig/smsConfig`)
71
76
 
77
+ 渠道参数要求(必须):
78
+ - 只要 `channels` 包含 `email`,必须提供 `emailConfig` 且包含 `to_address`
79
+ - 只要 `channels` 包含 `call`,必须提供 `callConfig` 且包含 `phone`
80
+ - 只要 `channels` 包含 `sms`,必须提供 `smsConfig` 且包含 `receiver`(或 `phone`)
81
+ - 只要 `channels` 包含 `dingtalk`,必须提供 `dingtalkConfig` 且包含 `cas_id`
82
+
72
83
  成功判定:
73
84
  - 返回 `success = true`
74
85
 
@@ -87,6 +98,23 @@ description: 智能盯盘插件,用于监控A股、港股、比特币等金融
87
98
  失败处理:
88
99
  - 返回失败原因并提示用户确认策略 ID
89
100
 
101
+ #### `notify.sms` / `notify.call` / `notify.email` / `notify.dingtalk`
102
+
103
+ 参数:
104
+ - `notify.sms`:必须提供手机号(`receiver` 或 `phone`)
105
+ - `notify.call`:必须提供 `phone`
106
+ - `notify.email`:必须提供 `to_address`
107
+ - `notify.dingtalk`:必须提供 `cas_id`
108
+
109
+ 成功判定:
110
+ - 返回 `success = true`
111
+
112
+ 失败处理:
113
+ - 明确返回失败原因,不要静默重试
114
+
115
+ 回执说明:
116
+ - 直连通知发送完成后,客户端会收到 `notify.sent` 事件(`overall_status/success_count/failed_count/deliveries`)
117
+
90
118
  ## 调用判定规则
91
119
 
92
120
  只有在用户明确表达以下意图时调用插件:
@@ -185,13 +213,21 @@ await runtime.queryTickerData({
185
213
 
186
214
  ## 渠道参数填写
187
215
 
188
- `openclaw` 渠道必传,`email` / `call` / `sms` 可选。如用户没明确说明使用邮件(email)、电话/外呼(call)、短信(sms) 通知提醒,则只需要传入`openclaw` 渠道。
216
+ `openclaw` 渠道必传,`email` / `call` / `sms` / `dingtalk` 可选。如用户没明确说明使用邮件(email)、电话/外呼(call)、短信(sms)、钉钉(dingtalk)通知提醒,则只需要传入`openclaw`渠道。
217
+
218
+ 但一旦用户选择了某个通知渠道,其配置参数必须完整填写:
219
+ - 选择 `email` 必须提供 `emailConfig.to_address`
220
+ - 选择 `call` 必须提供 `callConfig.phone`
221
+ - 选择 `sms` 必须提供 `smsConfig.receiver`(或 `phone`)
222
+ - 选择 `dingtalk` 必须提供 `dingtalkConfig.cas_id`
189
223
 
190
224
  ### email 参数(emailConfig)
191
- - `to_address`:收件人邮箱(必填)
225
+ - `to_address`:收件人邮箱(必填,缺失不可创建/不可发送)
192
226
  - `template_id`:邮件模板 ID(必填,默认为4,不需要修改)
193
- - `template_params`:模板变量(可选)
194
-
227
+ - `template_params`:模板变量
228
+ - `title`: 收到邮件的标题
229
+ - `product_name`: 产品名称
230
+ - `content`: 消息内容
195
231
  示例:
196
232
  ```javascript
197
233
  emailConfig: {
@@ -199,15 +235,17 @@ emailConfig: {
199
235
  template_id: 4,
200
236
  template_params: {
201
237
  title: '监控提醒',
202
- product_name: '比特币'
238
+ product_name: '比特币',
239
+ content: '测试消息1'
203
240
  }
204
241
  }
205
242
  ```
243
+ 用户收到的是一封title为"监控提醒",内容为"测试消息1"的一封邮件
206
244
 
207
245
  ### call 参数(callConfig)
208
- - `phone`:手机号(必填)
209
- - `customer_name`:客户名称(可选)
210
- - `condition`:外呼内容(可选,默认用触发消息,如不需要自定义可使用默认消息)
246
+ - `phone`:手机号(必填,缺失不可创建/不可发送)
247
+ - `customer_name`:客户名称
248
+ - `condition`:外呼内容
211
249
 
212
250
  示例:
213
251
  ```javascript
@@ -217,20 +255,42 @@ callConfig: {
217
255
  condition: '比特币价格突破阈值'
218
256
  }
219
257
  ```
258
+ 用户收到的是一通打给手机号码为13800138000的电话,电话内容为'比特币价格突破阈值'
259
+
220
260
 
221
261
  ### sms 参数(smsConfig)
222
- - `receiver`:手机号(必填;也可传 `phone`,推荐 `receiver`)
262
+ - `receiver`:手机号(必填,必须是纯数字;缺失不可创建/不可发送)
223
263
  - `template_id`:短信模板 ID(可选,默认 90010,不需要修改)
224
- - `content`:短信变量内容(可选,默认用触发消息,如不需要自定义可使用默认消息)
264
+ - `content`:短信变量内容
225
265
 
226
266
  示例:
227
267
  ```javascript
228
268
  smsConfig: {
229
- receiver: '13968617776',
269
+ receiver: '13800138000',
230
270
  template_id: 90010,
231
271
  content: '测试消息1'
232
272
  }
233
273
  ```
274
+ 用户收到的是一封发送给手机号码为13800138000的短信,短信内容为'测试消息1'
275
+
276
+
277
+ ### 钉钉 参数(dingtalkConfig)
278
+ - `cas_id`:钉钉用户ID(必填,缺失不可创建/不可发送)
279
+ - `template_id`:钉钉模板 ID(可选,默认 3,不需要修改)
280
+ - `msg_type`: 消息类型:text/markdown,默认 text
281
+ - `content`:消息内容
282
+
283
+ 示例:
284
+ ```javascript
285
+ dingtalkConfig: {
286
+ cas_id: 'user.dingtalk',
287
+ template_id: 3,
288
+ msg_type: "text",
289
+ content: "测试消息1"
290
+ }
291
+ ```
292
+ 用户收到的是一条发送给钉钉号为user.dingtalk的单聊消息,消息内容为'测试消息1'
293
+
234
294
 
235
295
  ## 支持的市场
236
296
 
@@ -278,7 +338,7 @@ variables: { threshold: 420, product_name: '腾讯控股' }
278
338
  当监控触发时:
279
339
  1. 解析 `market_data` 获取价格、涨跌幅等信息
280
340
  2. 发送提醒到用户当前对话的渠道(群聊/私聊)
281
- 3. `openclaw` 渠道必传,`email/call/sms` 可按需附加
341
+ 3. `openclaw` 渠道必传,`email/call/sms/dingtalk` 可按需附加
282
342
  4. 根据触发消息构建友好的提醒文案
283
343
 
284
344
  如果创建失败(`watch.create.result.success=false`):
@@ -98,6 +98,10 @@ export class OpenClawPluginAdapter {
98
98
  channelConfigs.sms = demand.smsConfig;
99
99
  if (!channels.includes('sms')) channels.push('sms');
100
100
  }
101
+ if (demand.dingtalkConfig) {
102
+ channelConfigs.dingtalk = demand.dingtalkConfig;
103
+ if (!channels.includes('dingtalk')) channels.push('dingtalk');
104
+ }
101
105
  if (!channels.includes('openclaw')) channels.unshift('openclaw');
102
106
  if (!channelConfigs.openclaw) channelConfigs.openclaw = {};
103
107
 
@@ -77,6 +77,10 @@ function mapDemandToCreatePayload(demand = {}) {
77
77
  channelConfigs.sms = demand.smsConfig;
78
78
  if (!channels.includes('sms')) channels.push('sms');
79
79
  }
80
+ if (demand.dingtalkConfig) {
81
+ channelConfigs.dingtalk = demand.dingtalkConfig;
82
+ if (!channels.includes('dingtalk')) channels.push('dingtalk');
83
+ }
80
84
  if (!channels.includes('openclaw')) channels.unshift('openclaw');
81
85
  if (!channelConfigs.openclaw) channelConfigs.openclaw = {};
82
86
 
@@ -114,6 +118,27 @@ function buildControlApi(startupPromise) {
114
118
  const runtime = await getReadyRuntime(startupPromise);
115
119
  return runtime.request('watch.create', payload);
116
120
  },
121
+ async sendNotification(input = {}) {
122
+ const runtime = await getReadyRuntime(startupPromise);
123
+ const channel = input.channel;
124
+ const payload = { ...(input.payload || {}) };
125
+ return runtime.request('notify.send', {
126
+ ...payload,
127
+ channel
128
+ });
129
+ },
130
+ async sendSms(payload = {}) {
131
+ return this.sendNotification({ channel: 'sms', payload });
132
+ },
133
+ async sendCall(payload = {}) {
134
+ return this.sendNotification({ channel: 'call', payload });
135
+ },
136
+ async sendEmail(payload = {}) {
137
+ return this.sendNotification({ channel: 'email', payload });
138
+ },
139
+ async sendDingtalk(payload = {}) {
140
+ return this.sendNotification({ channel: 'dingtalk', payload });
141
+ },
117
142
  async submitWatchDemand(demand = {}) {
118
143
  const runtime = await getReadyRuntime(startupPromise);
119
144
  return runtime.request('watch.create', mapDemandToCreatePayload(demand));
@@ -133,11 +158,20 @@ function buildControlApi(startupPromise) {
133
158
  };
134
159
  }
135
160
 
136
- function tryRegisterTool(registerTool, name, description, handler) {
161
+ function tryRegisterTool(registerTool, name, description, parameters, handler) {
137
162
  if (typeof registerTool !== 'function') return;
163
+ const schema =
164
+ parameters || {
165
+ type: 'object',
166
+ additionalProperties: true,
167
+ properties: {}
168
+ };
169
+
138
170
  const def = {
139
171
  name,
140
172
  description,
173
+ parameters: schema,
174
+ inputSchema: schema,
141
175
  handler,
142
176
  execute: async (_toolCallId, params) => handler(params || {})
143
177
  };
@@ -170,20 +204,122 @@ function tryRegisterTool(registerTool, name, description, handler) {
170
204
 
171
205
  function registerControlTools(api, controlApi) {
172
206
  const registerTool = api?.registerTool || api?.runtime?.registerTool;
173
- tryRegisterTool(registerTool, 'watch.query_ticker', 'Query ticker data', (args) =>
174
- controlApi.queryTickerData(args || {})
207
+
208
+ tryRegisterTool(
209
+ registerTool,
210
+ 'watch_query_ticker',
211
+ 'Query ticker data',
212
+ {
213
+ type: 'object',
214
+ additionalProperties: true,
215
+ properties: {
216
+ stock_code: { type: 'string' },
217
+ product_code: { type: 'string' },
218
+ product_type: { type: 'string' },
219
+ market: { type: 'string' }
220
+ }
221
+ },
222
+ (args) => controlApi.queryTickerData(args || {})
175
223
  );
176
- tryRegisterTool(registerTool, 'watch.create', 'Create watch strategy', (args) =>
177
- controlApi.createWatch(args || {})
224
+
225
+ tryRegisterTool(
226
+ registerTool,
227
+ 'notify_sms',
228
+ 'Send SMS notification',
229
+ {
230
+ type: 'object',
231
+ additionalProperties: true,
232
+ properties: {}
233
+ },
234
+ (args) => controlApi.sendSms(args || {})
178
235
  );
179
- tryRegisterTool(registerTool, 'watch.pause', 'Pause watch strategy', (args) =>
180
- controlApi.pauseWatch(args?.strategyId || args?.strategy_id)
236
+
237
+ tryRegisterTool(
238
+ registerTool,
239
+ 'notify_call',
240
+ 'Send phone call notification',
241
+ {
242
+ type: 'object',
243
+ additionalProperties: true,
244
+ properties: {}
245
+ },
246
+ (args) => controlApi.sendCall(args || {})
181
247
  );
182
- tryRegisterTool(registerTool, 'watch.activate', 'Activate watch strategy', (args) =>
183
- controlApi.activateWatch(args?.strategyId || args?.strategy_id)
248
+
249
+ tryRegisterTool(
250
+ registerTool,
251
+ 'notify_email',
252
+ 'Send email notification',
253
+ {
254
+ type: 'object',
255
+ additionalProperties: true,
256
+ properties: {}
257
+ },
258
+ (args) => controlApi.sendEmail(args || {})
184
259
  );
185
- tryRegisterTool(registerTool, 'watch.remove', 'Delete watch strategy', (args) =>
186
- controlApi.deleteWatch(args?.strategyId || args?.strategy_id)
260
+
261
+ tryRegisterTool(
262
+ registerTool,
263
+ 'notify_dingtalk',
264
+ 'Send dingtalk notification',
265
+ {
266
+ type: 'object',
267
+ additionalProperties: true,
268
+ properties: {}
269
+ },
270
+ (args) => controlApi.sendDingtalk(args || {})
271
+ );
272
+
273
+ tryRegisterTool(
274
+ registerTool,
275
+ 'watch_create',
276
+ 'Create watch strategy',
277
+ {
278
+ type: 'object',
279
+ additionalProperties: true,
280
+ properties: {
281
+ product_code: { type: 'string' },
282
+ product_type: { type: 'string' },
283
+ operator_type: { type: 'string' },
284
+ operator_parameters: { type: 'object' },
285
+ channels: { type: 'array', items: { type: 'string' } },
286
+ channel_configs: { type: 'object' }
287
+ }
288
+ },
289
+ (args) => controlApi.createWatch(args || {})
290
+ );
291
+
292
+ const strategySchema = {
293
+ type: 'object',
294
+ additionalProperties: true,
295
+ properties: {
296
+ strategy_id: { type: 'string' },
297
+ strategyId: { type: 'string' }
298
+ }
299
+ };
300
+
301
+ tryRegisterTool(
302
+ registerTool,
303
+ 'watch_pause',
304
+ 'Pause watch strategy',
305
+ strategySchema,
306
+ (args) => controlApi.pauseWatch(args?.strategyId || args?.strategy_id)
307
+ );
308
+
309
+ tryRegisterTool(
310
+ registerTool,
311
+ 'watch_activate',
312
+ 'Activate watch strategy',
313
+ strategySchema,
314
+ (args) => controlApi.activateWatch(args?.strategyId || args?.strategy_id)
315
+ );
316
+
317
+ tryRegisterTool(
318
+ registerTool,
319
+ 'watch_remove',
320
+ 'Delete watch strategy',
321
+ strategySchema,
322
+ (args) => controlApi.deleteWatch(args?.strategyId || args?.strategy_id)
187
323
  );
188
324
  }
189
325
 
@@ -194,11 +330,9 @@ const plugin = {
194
330
  register(api) {
195
331
  const pluginConfig =
196
332
  api?.config?.plugins?.entries?.['openclaw-glance-plugin']?.config ||
197
- api?.config?.plugins?.entries?.openclawGlancePlugin?.config ||
198
- api?.config?.plugins?.['openclaw-glance-plugin']?.config ||
199
- api?.config?.plugins?.openclawGlancePlugin?.config ||
200
333
  api?.config?.plugins?.entries?.['glance-bridge']?.config ||
201
334
  api?.config?.plugins?.entries?.glanceBridge?.config ||
335
+ api?.config?.plugins?.['openclaw-glance-plugin']?.config ||
202
336
  api?.config?.plugins?.['glance-bridge']?.config ||
203
337
  api?.config?.plugins?.glanceBridge?.config ||
204
338
  {};