openclaw-glance-plugin 0.1.27 → 0.1.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/README.md +1 -4
- package/openclaw.plugin.json +1 -2
- package/package.json +1 -1
- package/skills/glance-watch/references/channels.md +21 -7
- package/skills/glance-watch/references/examples.md +1 -5
- package/skills/glance-watch/references/watch-contract.md +8 -2
- package/src/OpenClawPluginAdapter.js +30 -4
- package/src/plugin/index.js +45 -22
package/README.md
CHANGED
|
@@ -107,8 +107,7 @@ await adapter.submitWatchDemand({
|
|
|
107
107
|
variables: { threshold: 8.97 },
|
|
108
108
|
channels: ['openclaw', 'email', 'call', 'sms', 'dingtalk'], // openclaw 必传,其它可选
|
|
109
109
|
emailConfig: {
|
|
110
|
-
to_address: 'demo@example.com'
|
|
111
|
-
template_id: 4
|
|
110
|
+
to_address: 'demo@example.com'
|
|
112
111
|
},
|
|
113
112
|
callConfig: {
|
|
114
113
|
phone: '13800138000',
|
|
@@ -116,12 +115,10 @@ await adapter.submitWatchDemand({
|
|
|
116
115
|
},
|
|
117
116
|
smsConfig: {
|
|
118
117
|
receiver: '13800138000',
|
|
119
|
-
template_id: 90010,
|
|
120
118
|
content: '测试短信'
|
|
121
119
|
},
|
|
122
120
|
dingtalkConfig: {
|
|
123
121
|
cas_id: 'user.dingtalk',
|
|
124
|
-
template_id: 3,
|
|
125
122
|
msg_type: 'text',
|
|
126
123
|
content: '测试钉钉消息'
|
|
127
124
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
|
|
22
22
|
### `notify_sms`
|
|
23
23
|
- `receiver`(或 `phone`)
|
|
24
|
-
- `template_id`
|
|
25
24
|
- `content`
|
|
26
25
|
|
|
27
26
|
### `notify_call`
|
|
@@ -31,16 +30,32 @@
|
|
|
31
30
|
|
|
32
31
|
### `notify_email`
|
|
33
32
|
- `to_address`
|
|
34
|
-
- `template_id`
|
|
35
33
|
- `title`
|
|
36
34
|
- `content`
|
|
37
35
|
|
|
38
36
|
### `notify_dingtalk`
|
|
39
37
|
- `cas_id`
|
|
40
|
-
- `template_id`
|
|
41
38
|
- `msg_type`(`text` 或 `markdown`)
|
|
42
39
|
- `content`
|
|
43
40
|
|
|
41
|
+
## 参数怎么填(直连通知)
|
|
42
|
+
|
|
43
|
+
- `notify_sms`
|
|
44
|
+
- `receiver/phone`:11 位手机号(优先用户本轮给出,否则从联系人 CSV 补)。
|
|
45
|
+
- `content`:一句可直接发送的短信正文,可带变量占位(如 `${price}`)。
|
|
46
|
+
- `notify_call`
|
|
47
|
+
- `phone`:11 位手机号。
|
|
48
|
+
- `customer_name`:称呼(优先用户输入,否则 CSV)。
|
|
49
|
+
- `condition`:电话播报内容,直接写“因为什么触发了通知”。
|
|
50
|
+
- `notify_email`
|
|
51
|
+
- `to_address`:收件邮箱(优先用户输入,否则 CSV)。
|
|
52
|
+
- `title`:邮件标题(简短,包含标的+触发事件)。
|
|
53
|
+
- `content`:邮件正文(包含当前值、阈值、建议动作)。
|
|
54
|
+
- `notify_dingtalk`
|
|
55
|
+
- `cas_id`:接收人钉钉账号(优先用户输入,否则 CSV 的 `dingtalk_cas_id`)。
|
|
56
|
+
- `msg_type`:`text`(普通文本)或 `markdown`(需要格式时)。
|
|
57
|
+
- `content`:消息正文。
|
|
58
|
+
|
|
44
59
|
## 联系人记忆(强约束)
|
|
45
60
|
|
|
46
61
|
联系人 CSV:`~/.openclaw/workspace/memory/watch-notify-contacts.csv`
|
|
@@ -74,12 +89,11 @@ rg -n '^sms,jinguo\.xie,' ~/.openclaw/workspace/memory/watch-notify-contacts.csv
|
|
|
74
89
|
## 常见禁止项
|
|
75
90
|
|
|
76
91
|
- 手机号含空格、中划线、`+86-` 等非纯数字
|
|
77
|
-
- `template_id` 传字符串
|
|
78
92
|
- `msg_type` 不是 `text/markdown`
|
|
79
93
|
|
|
80
94
|
## `watch_create` 渠道必填映射
|
|
81
95
|
|
|
82
|
-
- 选 `email`:`channel_configs.email.to_address/
|
|
96
|
+
- 选 `email`:`channel_configs.email.to_address/title/content`
|
|
83
97
|
- 选 `call`:`channel_configs.call.phone/customer_name/condition`
|
|
84
|
-
- 选 `sms`:`channel_configs.sms.receiver(或phone)/
|
|
85
|
-
- 选 `dingtalk`:`channel_configs.dingtalk.cas_id/
|
|
98
|
+
- 选 `sms`:`channel_configs.sms.receiver(或phone)/content`
|
|
99
|
+
- 选 `dingtalk`:`channel_configs.dingtalk.cas_id/msg_type/content`
|
|
@@ -49,7 +49,6 @@ await watch_create({
|
|
|
49
49
|
},
|
|
50
50
|
sms: {
|
|
51
51
|
receiver: '13800138000',
|
|
52
|
-
template_id: 90010,
|
|
53
52
|
content: '浦发银行触发盯盘条件,当前价 ${price}'
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -72,10 +71,9 @@ await watch_remove({ strategy_id: 's_123' })
|
|
|
72
71
|
### 2.1 发送短信(含联系人 CSV 补值)
|
|
73
72
|
|
|
74
73
|
```javascript
|
|
75
|
-
// Step 0: 先查联系人 CSV,补 receiver
|
|
74
|
+
// Step 0: 先查联系人 CSV,补 receiver
|
|
76
75
|
await notify_sms({
|
|
77
76
|
receiver: '13800138000',
|
|
78
|
-
template_id: 90010,
|
|
79
77
|
content: '测试短信:比特币跌幅超过2%'
|
|
80
78
|
})
|
|
81
79
|
|
|
@@ -93,14 +91,12 @@ await notify_call({
|
|
|
93
91
|
|
|
94
92
|
await notify_email({
|
|
95
93
|
to_address: 'demo@example.com',
|
|
96
|
-
template_id: 4,
|
|
97
94
|
title: '盯盘提醒',
|
|
98
95
|
content: 'BTCUSDT 触发阈值'
|
|
99
96
|
})
|
|
100
97
|
|
|
101
98
|
await notify_dingtalk({
|
|
102
99
|
cas_id: 'user.dingtalk',
|
|
103
|
-
template_id: 3,
|
|
104
100
|
msg_type: 'text',
|
|
105
101
|
content: '盯盘触发:BTCUSDT'
|
|
106
102
|
})
|
|
@@ -66,19 +66,25 @@ OpenClaw 路由约束(当 `channels` 包含 `openclaw`):
|
|
|
66
66
|
},
|
|
67
67
|
dingtalk: {
|
|
68
68
|
cas_id: 'jinguo.xie',
|
|
69
|
-
template_id: 3,
|
|
70
69
|
msg_type: 'text',
|
|
71
70
|
content: '比特币跌幅超2%!当前价格 ${price},跌幅 ${change_percent}%。建议卖出!'
|
|
72
71
|
},
|
|
73
72
|
sms: {
|
|
74
73
|
receiver: '13800138000',
|
|
75
|
-
template_id: 90010,
|
|
76
74
|
content: '比特币跌幅超2%!当前价格 ${price},建议卖出!'
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
}
|
|
80
78
|
```
|
|
81
79
|
|
|
80
|
+
参数填写指引(创建策略时):
|
|
81
|
+
- `channel_configs.sms.receiver`:手机号,优先本轮用户输入,否则联系人 CSV。
|
|
82
|
+
- `channel_configs.sms.content`:短信正文,写清触发条件+关键行情值。
|
|
83
|
+
- `channel_configs.email.to_address`:收件邮箱,优先本轮输入,否则 CSV。
|
|
84
|
+
- `channel_configs.email.title/content`:标题写“标的 + 触发事件”,正文写“当前值 + 阈值 + 建议动作”。
|
|
85
|
+
- `channel_configs.call.phone/customer_name/condition`:分别为号码、称呼、电话播报文案。
|
|
86
|
+
- `channel_configs.dingtalk.cas_id`:钉钉接收账号;`msg_type` 用 `text` 或 `markdown`;`content` 为消息正文。
|
|
87
|
+
|
|
82
88
|
成功判定:
|
|
83
89
|
- `success === true`
|
|
84
90
|
|
|
@@ -7,12 +7,38 @@ import {
|
|
|
7
7
|
import { OpenClawBridgeClient } from './OpenClawBridgeClient.js';
|
|
8
8
|
import { extractOpenclawRoutingFromRecord } from './openclawRouting.js';
|
|
9
9
|
|
|
10
|
+
const CHANNEL_TEMPLATE_DEFAULTS = Object.freeze({
|
|
11
|
+
sms: 90010,
|
|
12
|
+
email: 4,
|
|
13
|
+
dingtalk: 3
|
|
14
|
+
});
|
|
15
|
+
|
|
10
16
|
/**
|
|
11
17
|
* 全局单例 Adapter 实例
|
|
12
18
|
* @type {OpenClawPluginAdapter|null}
|
|
13
19
|
*/
|
|
14
20
|
let globalAdapterInstance = null;
|
|
15
21
|
|
|
22
|
+
function withChannelTemplateDefaults(channel, config) {
|
|
23
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
const defaultTemplateId = CHANNEL_TEMPLATE_DEFAULTS[channel];
|
|
27
|
+
if (defaultTemplateId == null || config.template_id != null) {
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
return { ...config, template_id: defaultTemplateId };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function applyChannelTemplateDefaults(channelConfigs = {}) {
|
|
34
|
+
return {
|
|
35
|
+
...channelConfigs,
|
|
36
|
+
sms: withChannelTemplateDefaults('sms', channelConfigs.sms),
|
|
37
|
+
email: withChannelTemplateDefaults('email', channelConfigs.email),
|
|
38
|
+
dingtalk: withChannelTemplateDefaults('dingtalk', channelConfigs.dingtalk)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
/**
|
|
17
43
|
* 获取全局单例 Adapter 实例
|
|
18
44
|
* @param {Object} clientOrOptions - 客户端实例或配置选项
|
|
@@ -87,13 +113,13 @@ export class OpenClawPluginAdapter {
|
|
|
87
113
|
.filter((x) => typeof x === 'string' && x.trim())
|
|
88
114
|
.map((x) => x.trim().toLowerCase())
|
|
89
115
|
: [];
|
|
90
|
-
const channelConfigs = { ...(demand.channelConfigs || {}) };
|
|
116
|
+
const channelConfigs = applyChannelTemplateDefaults({ ...(demand.channelConfigs || {}) });
|
|
91
117
|
|
|
92
118
|
if (demand.openclawConfig) {
|
|
93
119
|
if (!channels.includes('openclaw')) channels.push('openclaw');
|
|
94
120
|
}
|
|
95
121
|
if (demand.emailConfig) {
|
|
96
|
-
channelConfigs.email = demand.emailConfig;
|
|
122
|
+
channelConfigs.email = withChannelTemplateDefaults('email', demand.emailConfig);
|
|
97
123
|
if (!channels.includes('email')) channels.push('email');
|
|
98
124
|
}
|
|
99
125
|
if (demand.callConfig) {
|
|
@@ -101,11 +127,11 @@ export class OpenClawPluginAdapter {
|
|
|
101
127
|
if (!channels.includes('call')) channels.push('call');
|
|
102
128
|
}
|
|
103
129
|
if (demand.smsConfig) {
|
|
104
|
-
channelConfigs.sms = demand.smsConfig;
|
|
130
|
+
channelConfigs.sms = withChannelTemplateDefaults('sms', demand.smsConfig);
|
|
105
131
|
if (!channels.includes('sms')) channels.push('sms');
|
|
106
132
|
}
|
|
107
133
|
if (demand.dingtalkConfig) {
|
|
108
|
-
channelConfigs.dingtalk = demand.dingtalkConfig;
|
|
134
|
+
channelConfigs.dingtalk = withChannelTemplateDefaults('dingtalk', demand.dingtalkConfig);
|
|
109
135
|
if (!channels.includes('dingtalk')) channels.push('dingtalk');
|
|
110
136
|
}
|
|
111
137
|
if (!channels.includes('openclaw')) channels.unshift('openclaw');
|
package/src/plugin/index.js
CHANGED
|
@@ -13,6 +13,11 @@ import { ProcessLock } from '../runtime/lock/ProcessLock.js';
|
|
|
13
13
|
/** 与 BridgeRuntime FINANCE_TABLE_REQUEST_TIMEOUT_MS 一致 */
|
|
14
14
|
const GATEWAY_TABLE_REQUEST_TIMEOUT_MS = 90_000;
|
|
15
15
|
const FUND_CODE_PATTERN = /^\d{6}\.OF$/i;
|
|
16
|
+
const CHANNEL_TEMPLATE_DEFAULTS = Object.freeze({
|
|
17
|
+
sms: 90010,
|
|
18
|
+
email: 4,
|
|
19
|
+
dingtalk: 3
|
|
20
|
+
});
|
|
16
21
|
|
|
17
22
|
let activeRuntime = null;
|
|
18
23
|
|
|
@@ -73,13 +78,13 @@ function mapDemandToCreatePayload(demand = {}) {
|
|
|
73
78
|
.filter((x) => typeof x === 'string' && x.trim())
|
|
74
79
|
.map((x) => x.trim().toLowerCase())
|
|
75
80
|
: [];
|
|
76
|
-
const channelConfigs = { ...(demand.channelConfigs || {}) };
|
|
81
|
+
const channelConfigs = applyChannelTemplateDefaults({ ...(demand.channelConfigs || {}) });
|
|
77
82
|
|
|
78
83
|
if (demand.openclawConfig) {
|
|
79
84
|
if (!channels.includes('openclaw')) channels.push('openclaw');
|
|
80
85
|
}
|
|
81
86
|
if (demand.emailConfig) {
|
|
82
|
-
channelConfigs.email = demand.emailConfig;
|
|
87
|
+
channelConfigs.email = withChannelTemplateDefaults('email', demand.emailConfig);
|
|
83
88
|
if (!channels.includes('email')) channels.push('email');
|
|
84
89
|
}
|
|
85
90
|
if (demand.callConfig) {
|
|
@@ -87,11 +92,11 @@ function mapDemandToCreatePayload(demand = {}) {
|
|
|
87
92
|
if (!channels.includes('call')) channels.push('call');
|
|
88
93
|
}
|
|
89
94
|
if (demand.smsConfig) {
|
|
90
|
-
channelConfigs.sms = demand.smsConfig;
|
|
95
|
+
channelConfigs.sms = withChannelTemplateDefaults('sms', demand.smsConfig);
|
|
91
96
|
if (!channels.includes('sms')) channels.push('sms');
|
|
92
97
|
}
|
|
93
98
|
if (demand.dingtalkConfig) {
|
|
94
|
-
channelConfigs.dingtalk = demand.dingtalkConfig;
|
|
99
|
+
channelConfigs.dingtalk = withChannelTemplateDefaults('dingtalk', demand.dingtalkConfig);
|
|
95
100
|
if (!channels.includes('dingtalk')) channels.push('dingtalk');
|
|
96
101
|
}
|
|
97
102
|
if (!channels.includes('openclaw')) channels.unshift('openclaw');
|
|
@@ -126,29 +131,47 @@ function mapDemandToCreatePayload(demand = {}) {
|
|
|
126
131
|
|
|
127
132
|
function mergeOpenclawChannelConfig(payload = {}, context = {}) {
|
|
128
133
|
const merged = { ...(payload || {}) };
|
|
129
|
-
const channelConfigs = { ...(merged.channel_configs || {}) };
|
|
130
|
-
const openclawConfig = { ...(channelConfigs.openclaw || {}) };
|
|
134
|
+
const channelConfigs = applyChannelTemplateDefaults({ ...(merged.channel_configs || {}) });
|
|
131
135
|
const routing = deriveOpenclawRouting({ params: merged, context });
|
|
136
|
+
merged.channel_configs = channelConfigs;
|
|
137
|
+
|
|
138
|
+
if (Object.keys(routing).length > 0) {
|
|
139
|
+
const openclawConfig = { ...(channelConfigs.openclaw || {}) };
|
|
140
|
+
channelConfigs.openclaw = {
|
|
141
|
+
...openclawConfig,
|
|
142
|
+
...routing
|
|
143
|
+
};
|
|
132
144
|
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
const channels = Array.isArray(merged.channels)
|
|
146
|
+
? merged.channels
|
|
147
|
+
.filter((x) => typeof x === 'string' && x.trim())
|
|
148
|
+
.map((x) => x.trim().toLowerCase())
|
|
149
|
+
: [];
|
|
150
|
+
if (!channels.includes('openclaw')) channels.unshift('openclaw');
|
|
151
|
+
merged.channels = channels;
|
|
135
152
|
}
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
...routing
|
|
140
|
-
};
|
|
154
|
+
return merged;
|
|
155
|
+
}
|
|
141
156
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
157
|
+
function withChannelTemplateDefaults(channel, config) {
|
|
158
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
159
|
+
return config;
|
|
160
|
+
}
|
|
161
|
+
const defaultTemplateId = CHANNEL_TEMPLATE_DEFAULTS[channel];
|
|
162
|
+
if (defaultTemplateId == null || config.template_id != null) {
|
|
163
|
+
return config;
|
|
164
|
+
}
|
|
165
|
+
return { ...config, template_id: defaultTemplateId };
|
|
166
|
+
}
|
|
148
167
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
168
|
+
function applyChannelTemplateDefaults(channelConfigs = {}) {
|
|
169
|
+
return {
|
|
170
|
+
...channelConfigs,
|
|
171
|
+
sms: withChannelTemplateDefaults('sms', channelConfigs.sms),
|
|
172
|
+
email: withChannelTemplateDefaults('email', channelConfigs.email),
|
|
173
|
+
dingtalk: withChannelTemplateDefaults('dingtalk', channelConfigs.dingtalk)
|
|
174
|
+
};
|
|
152
175
|
}
|
|
153
176
|
|
|
154
177
|
function assertWatchCreateSupported(payload = {}) {
|
|
@@ -263,7 +286,7 @@ function buildControlApi(startupPromise) {
|
|
|
263
286
|
'notify.send requires input.channel to be one of: sms, email, call, dingtalk'
|
|
264
287
|
);
|
|
265
288
|
}
|
|
266
|
-
const payload = { ...(input.payload || {}) };
|
|
289
|
+
const payload = withChannelTemplateDefaults(ch, { ...(input.payload || {}) });
|
|
267
290
|
return runtime.request('notify.send', {
|
|
268
291
|
...payload,
|
|
269
292
|
channel: ch
|