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 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
  }
@@ -14,7 +14,6 @@
14
14
  "lockDir": {
15
15
  "type": "string"
16
16
  }
17
- },
18
- "required": ["token"]
17
+ }
19
18
  }
20
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-glance-plugin",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "OpenClaw plugin client for ticker-monitor openclaw-bridge",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -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/template_id/title/content`
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)/template_id/content`
85
- - 选 `dingtalk`:`channel_configs.dingtalk.cas_id/template_id/msg_type/content`
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/template_id
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');
@@ -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
- if (Object.keys(routing).length === 0) {
134
- return merged;
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
- channelConfigs.openclaw = {
138
- ...openclawConfig,
139
- ...routing
140
- };
154
+ return merged;
155
+ }
141
156
 
142
- const channels = Array.isArray(merged.channels)
143
- ? merged.channels
144
- .filter((x) => typeof x === 'string' && x.trim())
145
- .map((x) => x.trim().toLowerCase())
146
- : [];
147
- if (!channels.includes('openclaw')) channels.unshift('openclaw');
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
- merged.channels = channels;
150
- merged.channel_configs = channelConfigs;
151
- return merged;
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