openclaw-glance-plugin 0.1.18 → 0.1.22
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/openclaw.plugin.json +0 -4
- package/package.json +1 -1
- package/skills/glance-watch/SKILL.md +57 -481
- package/skills/glance-watch/references/channels.md +90 -0
- package/skills/glance-watch/references/examples.md +43 -0
- package/skills/glance-watch/references/query-and-symbol.md +69 -0
- package/skills/glance-watch/references/troubleshooting.md +40 -0
- package/skills/glance-watch/references/watch-contract.md +132 -0
- package/src/config/runtime-config.js +1 -15
- package/src/plugin/index.js +19 -41
- package/src/plugin/watch-notify-contacts.js +0 -478
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# 渠道与通知参数
|
|
2
|
+
|
|
3
|
+
## 渠道策略
|
|
4
|
+
|
|
5
|
+
- `openclaw` 渠道默认必带(用于回当前会话)。
|
|
6
|
+
- `email/call/sms/dingtalk` 仅在用户明确要求时添加。
|
|
7
|
+
|
|
8
|
+
## OpenClaw 会话路由
|
|
9
|
+
|
|
10
|
+
当 `channels` 包含 `openclaw` 时,`channel_configs.openclaw` 必须可定位当前会话。
|
|
11
|
+
|
|
12
|
+
常用字段:
|
|
13
|
+
- `channel` 或 `source_channel`
|
|
14
|
+
- `account_id`
|
|
15
|
+
- `session_key`
|
|
16
|
+
- `conversation_id` 或 `chat_id`
|
|
17
|
+
|
|
18
|
+
约束:
|
|
19
|
+
- 拿不到路由信息时,不得传空对象 `openclaw: {}` 假装已配置。
|
|
20
|
+
- 宿主传入 `context` 时,插件运行时只负责合并 openclaw 路由字段。
|
|
21
|
+
|
|
22
|
+
## 联系人记忆(Agent/OpenClaw 侧 CSV)
|
|
23
|
+
|
|
24
|
+
真源:`~/.openclaw/workspace/memory/watch-notify-contacts.csv`
|
|
25
|
+
|
|
26
|
+
建议表头:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
channel,sender_id,sender_name,phone,email,dingtalk_cas_id,customer_name,updated_at,notes
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
执行要求:
|
|
33
|
+
1. 凡是 `watch.create` 或 `notify.*` 涉及 `call/sms/email/dingtalk`,调用前必须先查询该 CSV。
|
|
34
|
+
2. 取值优先级固定为:
|
|
35
|
+
- 本轮用户明确提供
|
|
36
|
+
- CSV 历史默认值
|
|
37
|
+
- 仍缺必填字段 -> 追问
|
|
38
|
+
3. 若用户在本轮提供了新的联系方式,且本次 `watch.create` / `notify.*` 成功,必须回写 CSV。
|
|
39
|
+
4. 联系人记忆只认该 CSV;不依赖、不读取、不提及 `watch-notify-contacts.json`。
|
|
40
|
+
5. 若 CSV 不存在,先创建带表头的文件,再写入首条联系人记录。
|
|
41
|
+
|
|
42
|
+
查询示例:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
rg -n '^dingtalk,jinguo\.xie,' ~/.openclaw/workspace/memory/watch-notify-contacts.csv
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
写回要求:
|
|
49
|
+
- 已有同一 `(channel, sender_id)` 记录时,更新该行,不重复追加。
|
|
50
|
+
- 不存在时,按表头字段顺序追加新行。
|
|
51
|
+
- `updated_at` 使用当前会话时区时间的 ISO-8601 字符串。
|
|
52
|
+
|
|
53
|
+
## `watch.create` 渠道必填
|
|
54
|
+
|
|
55
|
+
- 选 `email` -> `channel_configs.email.to_address/template_id/title/content`
|
|
56
|
+
- 选 `call` -> `channel_configs.call.phone/customer_name/condition`
|
|
57
|
+
- 选 `sms` -> `channel_configs.sms.receiver(或phone)/template_id/content`
|
|
58
|
+
- 选 `dingtalk` -> `channel_configs.dingtalk.cas_id/template_id/msg_type/content`
|
|
59
|
+
|
|
60
|
+
## `notify.*` 参数
|
|
61
|
+
|
|
62
|
+
- `notify.sms`: `receiver(或phone)`、`template_id`、`content`
|
|
63
|
+
- `notify.call`: `phone`、`customer_name`、`condition`
|
|
64
|
+
- `notify.email`: `to_address`、`template_id`、`title`、`content`
|
|
65
|
+
- `notify.dingtalk`: `cas_id`、`template_id`、`msg_type`、`content`
|
|
66
|
+
|
|
67
|
+
固定模板:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// notify.sms
|
|
71
|
+
{ receiver: '13800138000', template_id: 90010, content: '测试消息1' }
|
|
72
|
+
|
|
73
|
+
// notify.call
|
|
74
|
+
{ phone: '13800138000', customer_name: 'Demo', condition: '比特币跌幅超过2%' }
|
|
75
|
+
|
|
76
|
+
// notify.email
|
|
77
|
+
{ to_address: 'demo@example.com', template_id: 4, title: '监控提醒', content: '测试消息1' }
|
|
78
|
+
|
|
79
|
+
// notify.dingtalk
|
|
80
|
+
{ cas_id: 'user.dingtalk', template_id: 3, msg_type: 'text', content: '测试消息1' }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
禁止项:
|
|
84
|
+
- 手机号含空格、`+86-`、中划线等非纯数字
|
|
85
|
+
- `template_id` 传字符串
|
|
86
|
+
- `msg_type` 非 `text/markdown`
|
|
87
|
+
- 手动传 `request_id`
|
|
88
|
+
|
|
89
|
+
成功判定:
|
|
90
|
+
- `success = true`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 典型示例
|
|
2
|
+
|
|
3
|
+
## 比特币监控
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
operator_type: 'rule'
|
|
7
|
+
operator_parameters: {
|
|
8
|
+
condition: 'price >= threshold and change_percent >= cp_threshold',
|
|
9
|
+
variables: { threshold: 73000, cp_threshold: 0.01, product_name: 'Bitcoin' }
|
|
10
|
+
}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
说明:`crypto` 不支持 `turnover_rate`。
|
|
14
|
+
|
|
15
|
+
## A 股监控
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
operator_type: 'rule'
|
|
19
|
+
operator_parameters: {
|
|
20
|
+
condition: 'price >= threshold and turnover_rate >= tr_threshold',
|
|
21
|
+
variables: { threshold: 12.5, tr_threshold: 0.01, product_name: '平安银行' }
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 港股监控
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
operator_type: 'rule'
|
|
29
|
+
operator_parameters: {
|
|
30
|
+
condition: 'price >= threshold',
|
|
31
|
+
variables: { threshold: 420, product_name: '腾讯控股' }
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 查询行情
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
await runtime.queryTickerData({
|
|
39
|
+
stockCode: '00700',
|
|
40
|
+
market: 'HK',
|
|
41
|
+
productType: 'hk_stock'
|
|
42
|
+
})
|
|
43
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 标的检索与行情查询
|
|
2
|
+
|
|
3
|
+
## 标的检索规则(必须遵循)
|
|
4
|
+
|
|
5
|
+
当不能直接确定 `product_code` / `product_type` 时,先查本地 CSV,再让用户确认。
|
|
6
|
+
|
|
7
|
+
数据文件(字段:`类型,代码,名称,完整代码,市场`):
|
|
8
|
+
- `data/stock_a.csv` -> A 股个股(`productType=stock`)
|
|
9
|
+
- `data/stock_hk.csv` -> 港股个股(`productType=hk_stock`)
|
|
10
|
+
- `data/index_a.csv` -> A 股指数(`productType=index`)
|
|
11
|
+
- `data/index_hk.csv` -> 港股指数(`productType=index`,查询常配 `market=HK`)
|
|
12
|
+
|
|
13
|
+
## 场景 1:用户只说名称
|
|
14
|
+
|
|
15
|
+
- 在 CSV 里按名称模糊搜索。
|
|
16
|
+
- 命中多条时,必须给出候选(代码 + 名称 + 市场)让用户确认。
|
|
17
|
+
- 不可自行猜测后直接创建策略。
|
|
18
|
+
|
|
19
|
+
## 场景 2:用户不知道代码或市场
|
|
20
|
+
|
|
21
|
+
- 用 `rg` 在 4 个 CSV 搜索名称或代码。
|
|
22
|
+
- 映射规则:
|
|
23
|
+
- A 股个股 -> `stock`
|
|
24
|
+
- 港股个股 -> `hk_stock`
|
|
25
|
+
- A 股指数 -> `index`
|
|
26
|
+
- 港股指数 -> `index`(`market=HK`)
|
|
27
|
+
- 结果不唯一时先追问。
|
|
28
|
+
|
|
29
|
+
## 推荐检索命令
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 按名称模糊查找
|
|
33
|
+
rg -n "平安银行|腾讯|沪深300|BTC" data/stock_a.csv data/stock_hk.csv data/index_a.csv
|
|
34
|
+
rg -n "恒生科技指数|恒生指数|HSTECH|HSI" data/index_hk.csv
|
|
35
|
+
|
|
36
|
+
# 按代码查找
|
|
37
|
+
rg -n "000001|00700|399001" data/stock_a.csv data/stock_hk.csv data/index_a.csv
|
|
38
|
+
rg -n "HSTECH|HSI|VHSI" data/index_hk.csv
|
|
39
|
+
|
|
40
|
+
# 无 rg 时兜底
|
|
41
|
+
grep -nE "平安银行|腾讯|沪深300|000001|00700|恒生科技指数|HSTECH" \
|
|
42
|
+
data/stock_a.csv data/stock_hk.csv data/index_a.csv data/index_hk.csv
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 行情查询流程(`watch.query_ticker`)
|
|
46
|
+
|
|
47
|
+
说明:
|
|
48
|
+
- 对外统一动作名是 `watch.query_ticker`。
|
|
49
|
+
- 文档中的 `runtime.queryTickerData(...)` 仅用于说明宿主运行时内部调用形态。
|
|
50
|
+
|
|
51
|
+
1. 先确定标的代码、市场、`productType`。
|
|
52
|
+
2. 调用查询动作。
|
|
53
|
+
3. 成功后反馈价格/涨跌幅,失败则返回错误并让用户确认代码或市场。
|
|
54
|
+
|
|
55
|
+
示例:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
await runtime.queryTickerData({
|
|
59
|
+
stockCode: '00700',
|
|
60
|
+
market: 'HK',
|
|
61
|
+
productType: 'hk_stock'
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
成功判定:
|
|
66
|
+
- `code = "000000"` 或 `success = true`
|
|
67
|
+
|
|
68
|
+
失败处理:
|
|
69
|
+
- 直接返回失败原因,不静默重试
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# 重试与排障
|
|
2
|
+
|
|
3
|
+
## 统一重试原则
|
|
4
|
+
|
|
5
|
+
- 超时或网络波动时可重试。
|
|
6
|
+
- 重试必须使用同一组 payload(字段和值不变)。
|
|
7
|
+
- `request_id` 由插件运行时自动生成并在同 payload 上复用。
|
|
8
|
+
|
|
9
|
+
## 创建策略失败(`watch.create`)
|
|
10
|
+
|
|
11
|
+
处理规则:
|
|
12
|
+
- 明确返回失败原因,不静默重试。
|
|
13
|
+
- 若报“未注册的算子类型”,将 `operator_type` 修正为 `rule` 后重试。
|
|
14
|
+
|
|
15
|
+
## 通知失败(`notify.*`)
|
|
16
|
+
|
|
17
|
+
处理规则:
|
|
18
|
+
- 优先返回 `code/error/hint`。
|
|
19
|
+
- `MISSING_REQUIRED_FIELD`:仅补缺失字段后重试。
|
|
20
|
+
- `UNSUPPORTED_MESSAGE_TYPE`:提示 bridge 版本不支持,需升级并重启。
|
|
21
|
+
- `UPSTREAM_UNAVAILABLE`:提示上游不可用或超时,稍后重试。
|
|
22
|
+
|
|
23
|
+
## 离线补发识别(`watch.triggered`)
|
|
24
|
+
|
|
25
|
+
满足任一条件视为离线补发:
|
|
26
|
+
- `delivery_mode = "offline_replay"`
|
|
27
|
+
- `replayed = true`
|
|
28
|
+
|
|
29
|
+
时间语义:
|
|
30
|
+
- `trigger_time`:原始触发时间
|
|
31
|
+
- `replayed_at`:补发时间
|
|
32
|
+
|
|
33
|
+
用户文案需明确“离线期间触发,当前为补发”。
|
|
34
|
+
|
|
35
|
+
## 触发后动作
|
|
36
|
+
|
|
37
|
+
1. 解析 `market_data` 提取价格、涨跌幅。
|
|
38
|
+
2. 通过 `openclaw` 回当前群/私聊。
|
|
39
|
+
3. 附加渠道按用户要求发送。
|
|
40
|
+
4. 生成简洁提醒文案。
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Watch 动作契约
|
|
2
|
+
|
|
3
|
+
## `watch.query_ticker`
|
|
4
|
+
|
|
5
|
+
参数:
|
|
6
|
+
- `stockCode`(或 `productCode`)
|
|
7
|
+
- `productType`
|
|
8
|
+
- `market`(`crypto` 可传空字符串)
|
|
9
|
+
|
|
10
|
+
成功判定:
|
|
11
|
+
- `code = "000000"` 或 `success = true`
|
|
12
|
+
|
|
13
|
+
失败处理:
|
|
14
|
+
- 返回失败原因
|
|
15
|
+
- 引导用户确认代码/市场后重试
|
|
16
|
+
|
|
17
|
+
## `watch.create`
|
|
18
|
+
|
|
19
|
+
最小参数:
|
|
20
|
+
- `product_code`
|
|
21
|
+
- `product_type`
|
|
22
|
+
- `operator_type`(固定 `rule`)
|
|
23
|
+
- `operator_parameters`(包含 `condition`、`variables`)
|
|
24
|
+
|
|
25
|
+
建议参数:
|
|
26
|
+
- `channels`(默认至少 `openclaw`)
|
|
27
|
+
- `channel_configs.*`
|
|
28
|
+
|
|
29
|
+
固定结构(字段名不可改):
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
{
|
|
33
|
+
product_code: 'BTCUSDT',
|
|
34
|
+
product_type: 'crypto',
|
|
35
|
+
operator_type: 'rule',
|
|
36
|
+
operator_parameters: {
|
|
37
|
+
condition: 'change_percent <= cp_threshold',
|
|
38
|
+
variables: {
|
|
39
|
+
cp_threshold: -0.02,
|
|
40
|
+
product_name: '比特币'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
channels: ['openclaw', 'dingtalk', 'sms'],
|
|
44
|
+
channel_configs: {
|
|
45
|
+
openclaw: {
|
|
46
|
+
channel: 'dingtalk',
|
|
47
|
+
account_id: 'default',
|
|
48
|
+
session_key: 'agent:main:dingtalk:group:<conversation_id>',
|
|
49
|
+
conversation_id: '<conversation_id>'
|
|
50
|
+
},
|
|
51
|
+
dingtalk: {
|
|
52
|
+
cas_id: 'jinguo.xie',
|
|
53
|
+
template_id: 3,
|
|
54
|
+
msg_type: 'text',
|
|
55
|
+
content: '比特币跌幅超2%!当前价格 ${price},跌幅 ${change_percent}%。建议卖出!'
|
|
56
|
+
},
|
|
57
|
+
sms: {
|
|
58
|
+
receiver: '13800138000',
|
|
59
|
+
template_id: 90010,
|
|
60
|
+
content: '比特币跌幅超2%!当前价格 ${price},建议卖出!'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
禁止项:
|
|
67
|
+
- `operator_type` 非 `rule`
|
|
68
|
+
- 顶层 `condition`
|
|
69
|
+
- `channel_configs` 渠道配置是字符串
|
|
70
|
+
- 用户未要求渠道却默认附加
|
|
71
|
+
|
|
72
|
+
成功判定:
|
|
73
|
+
- `success = true`
|
|
74
|
+
|
|
75
|
+
失败处理:
|
|
76
|
+
- 明确返回失败原因,不静默重试
|
|
77
|
+
- 超时/网络波动重试时,使用同一 payload(字段和值不变)
|
|
78
|
+
- `request_id` 由插件运行时自动生成并复用,大模型不手动传
|
|
79
|
+
|
|
80
|
+
## `watch.pause` / `watch.activate` / `watch.remove`
|
|
81
|
+
|
|
82
|
+
参数:
|
|
83
|
+
- `strategyId`(或 `strategy_id`)
|
|
84
|
+
|
|
85
|
+
成功判定:
|
|
86
|
+
- `success = true`
|
|
87
|
+
|
|
88
|
+
失败处理:
|
|
89
|
+
- 返回失败原因并提示用户确认策略 ID
|
|
90
|
+
|
|
91
|
+
## `watch.list`
|
|
92
|
+
|
|
93
|
+
可选参数:
|
|
94
|
+
- `status`: `active/paused/completed/failed/expired`
|
|
95
|
+
- `product_code`(或 `productCode`)
|
|
96
|
+
|
|
97
|
+
成功判定:
|
|
98
|
+
- `success = true`
|
|
99
|
+
- `data.total` 为命中数量
|
|
100
|
+
- `data.strategies` 为策略列表
|
|
101
|
+
|
|
102
|
+
失败处理:
|
|
103
|
+
- 返回失败原因,不静默重试
|
|
104
|
+
- 空结果明确告知“当前条件下没有策略”
|
|
105
|
+
|
|
106
|
+
安全约束:
|
|
107
|
+
- 仅可查询当前连接用户自己的策略
|
|
108
|
+
- 不通过 `user_id/use_id` 越权查询
|
|
109
|
+
|
|
110
|
+
## 调用前最终检查
|
|
111
|
+
|
|
112
|
+
1. `watch.create` 使用 snake_case:`product_code/product_type/operator_type/operator_parameters/channel_configs`
|
|
113
|
+
2. `operator_type` 固定 `rule`
|
|
114
|
+
3. `operator_parameters.condition` 与 `variables` 同时存在
|
|
115
|
+
4. `channels` 与 `channel_configs` 一一对应
|
|
116
|
+
5. 渠道配置是对象,不是 JSON 字符串
|
|
117
|
+
6. 默认不手动传 `request_id`
|
|
118
|
+
|
|
119
|
+
## 买卖意图与条件方向
|
|
120
|
+
|
|
121
|
+
| 用户意图 | 条件方向 |
|
|
122
|
+
|---------|---------|
|
|
123
|
+
| 买入(逢低) | `price <= threshold` |
|
|
124
|
+
| 卖出(止盈/止损) | `price >= threshold` |
|
|
125
|
+
|
|
126
|
+
判断规则:
|
|
127
|
+
1. 用户明确说“涨到/跌到”时,按方向直接生成条件。
|
|
128
|
+
2. 用户只说“到了 XX 提醒我”时,必须追问“买还是卖”。
|
|
129
|
+
3. 常见映射:
|
|
130
|
+
- `涨到/涨过/突破/冲到` -> `price >= threshold`
|
|
131
|
+
- `跌到/跌破/回调到/回到` -> `price <= threshold`
|
|
132
|
+
- `到了/到达/价格到` -> 方向不明确,需追问
|
|
@@ -2,19 +2,6 @@ import os from 'node:os';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
|
|
5
|
-
export function resolveContactsStorePath({ env = process.env, pluginConfig = {} } = {}) {
|
|
6
|
-
const explicit = pick(
|
|
7
|
-
pluginConfig,
|
|
8
|
-
['contactsStorePath', 'contacts_store_path'],
|
|
9
|
-
pick(env, ['OPENCLAW_CONTACTS_STORE_PATH'])
|
|
10
|
-
);
|
|
11
|
-
if (explicit) {
|
|
12
|
-
return String(explicit).trim();
|
|
13
|
-
}
|
|
14
|
-
const homeDir = pick(env, ['HOME', 'USERPROFILE']) || os.homedir();
|
|
15
|
-
return path.join(String(homeDir), '.openclaw', 'workspace', 'memory', 'watch-notify-contacts.json');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
5
|
import { ProcessLock } from '../runtime/lock/ProcessLock.js';
|
|
19
6
|
|
|
20
7
|
const DEFAULT_BASE_WS_URL = 'wss://glanceup-pre.100credit.cn';
|
|
@@ -78,7 +65,6 @@ export function resolveRuntimeConfig({ env = process.env, pluginConfig = {} } =
|
|
|
78
65
|
baseWsUrl,
|
|
79
66
|
token,
|
|
80
67
|
lockDir,
|
|
81
|
-
lockKey
|
|
82
|
-
contactsStorePath: resolveContactsStorePath({ env, pluginConfig })
|
|
68
|
+
lockKey
|
|
83
69
|
};
|
|
84
70
|
}
|
package/src/plugin/index.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
import { resolveRuntimeConfig
|
|
1
|
+
import { resolveRuntimeConfig } from '../config/runtime-config.js';
|
|
2
2
|
import { extractOpenclawRoutingFromRecord, deriveOpenclawRouting } from '../openclawRouting.js';
|
|
3
3
|
import { BridgeRuntime } from '../runtime/BridgeRuntime.js';
|
|
4
|
-
import {
|
|
5
|
-
mergeAndPersistNotifyContacts,
|
|
6
|
-
mergeAndPersistWatchContacts
|
|
7
|
-
} from './watch-notify-contacts.js';
|
|
8
4
|
import { PluginDispatcher } from '../runtime/dispatchers/PluginDispatcher.js';
|
|
9
5
|
import { ProcessLock } from '../runtime/lock/ProcessLock.js';
|
|
10
6
|
|
|
@@ -145,7 +141,7 @@ function mergeOpenclawChannelConfig(payload = {}, context = {}) {
|
|
|
145
141
|
return merged;
|
|
146
142
|
}
|
|
147
143
|
|
|
148
|
-
function buildControlApi(startupPromise
|
|
144
|
+
function buildControlApi(startupPromise) {
|
|
149
145
|
return {
|
|
150
146
|
async queryTickerData(query = {}) {
|
|
151
147
|
const runtime = await getReadyRuntime(startupPromise);
|
|
@@ -164,14 +160,9 @@ function buildControlApi(startupPromise, contactsStorePath) {
|
|
|
164
160
|
async createWatch(payload = {}, context = {}) {
|
|
165
161
|
const runtime = await getReadyRuntime(startupPromise);
|
|
166
162
|
const normalized = mergeOpenclawChannelConfig(payload, context);
|
|
167
|
-
|
|
168
|
-
contactsStorePath,
|
|
169
|
-
normalized,
|
|
170
|
-
context
|
|
171
|
-
);
|
|
172
|
-
return runtime.request('watch.create', filled);
|
|
163
|
+
return runtime.request('watch.create', normalized);
|
|
173
164
|
},
|
|
174
|
-
async sendNotification(input = {}
|
|
165
|
+
async sendNotification(input = {}) {
|
|
175
166
|
const runtime = await getReadyRuntime(startupPromise);
|
|
176
167
|
const ch = String(input.channel ?? '')
|
|
177
168
|
.trim()
|
|
@@ -183,39 +174,28 @@ function buildControlApi(startupPromise, contactsStorePath) {
|
|
|
183
174
|
);
|
|
184
175
|
}
|
|
185
176
|
const payload = { ...(input.payload || {}) };
|
|
186
|
-
const merged = await mergeAndPersistNotifyContacts(
|
|
187
|
-
contactsStorePath,
|
|
188
|
-
ch,
|
|
189
|
-
payload,
|
|
190
|
-
context
|
|
191
|
-
);
|
|
192
177
|
return runtime.request('notify.send', {
|
|
193
|
-
...
|
|
178
|
+
...payload,
|
|
194
179
|
channel: ch
|
|
195
180
|
});
|
|
196
181
|
},
|
|
197
|
-
async sendSms(payload = {}
|
|
198
|
-
return this.sendNotification({ channel: 'sms', payload }
|
|
182
|
+
async sendSms(payload = {}) {
|
|
183
|
+
return this.sendNotification({ channel: 'sms', payload });
|
|
199
184
|
},
|
|
200
|
-
async sendCall(payload = {}
|
|
201
|
-
return this.sendNotification({ channel: 'call', payload }
|
|
185
|
+
async sendCall(payload = {}) {
|
|
186
|
+
return this.sendNotification({ channel: 'call', payload });
|
|
202
187
|
},
|
|
203
|
-
async sendEmail(payload = {}
|
|
204
|
-
return this.sendNotification({ channel: 'email', payload }
|
|
188
|
+
async sendEmail(payload = {}) {
|
|
189
|
+
return this.sendNotification({ channel: 'email', payload });
|
|
205
190
|
},
|
|
206
|
-
async sendDingtalk(payload = {}
|
|
207
|
-
return this.sendNotification({ channel: 'dingtalk', payload }
|
|
191
|
+
async sendDingtalk(payload = {}) {
|
|
192
|
+
return this.sendNotification({ channel: 'dingtalk', payload });
|
|
208
193
|
},
|
|
209
194
|
async submitWatchDemand(demand = {}, context = {}) {
|
|
210
195
|
const runtime = await getReadyRuntime(startupPromise);
|
|
211
196
|
const payload = mapDemandToCreatePayload(demand);
|
|
212
197
|
const normalized = mergeOpenclawChannelConfig(payload, context);
|
|
213
|
-
|
|
214
|
-
contactsStorePath,
|
|
215
|
-
normalized,
|
|
216
|
-
context
|
|
217
|
-
);
|
|
218
|
-
return runtime.request('watch.create', filled);
|
|
198
|
+
return runtime.request('watch.create', normalized);
|
|
219
199
|
},
|
|
220
200
|
async pauseWatch(strategyId) {
|
|
221
201
|
const runtime = await getReadyRuntime(startupPromise);
|
|
@@ -310,7 +290,7 @@ function registerControlTools(api, controlApi) {
|
|
|
310
290
|
additionalProperties: true,
|
|
311
291
|
properties: {}
|
|
312
292
|
},
|
|
313
|
-
(args
|
|
293
|
+
(args) => controlApi.sendSms(args || {})
|
|
314
294
|
);
|
|
315
295
|
|
|
316
296
|
tryRegisterTool(
|
|
@@ -322,7 +302,7 @@ function registerControlTools(api, controlApi) {
|
|
|
322
302
|
additionalProperties: true,
|
|
323
303
|
properties: {}
|
|
324
304
|
},
|
|
325
|
-
(args
|
|
305
|
+
(args) => controlApi.sendCall(args || {})
|
|
326
306
|
);
|
|
327
307
|
|
|
328
308
|
tryRegisterTool(
|
|
@@ -334,7 +314,7 @@ function registerControlTools(api, controlApi) {
|
|
|
334
314
|
additionalProperties: true,
|
|
335
315
|
properties: {}
|
|
336
316
|
},
|
|
337
|
-
(args
|
|
317
|
+
(args) => controlApi.sendEmail(args || {})
|
|
338
318
|
);
|
|
339
319
|
|
|
340
320
|
tryRegisterTool(
|
|
@@ -346,7 +326,7 @@ function registerControlTools(api, controlApi) {
|
|
|
346
326
|
additionalProperties: true,
|
|
347
327
|
properties: {}
|
|
348
328
|
},
|
|
349
|
-
(args
|
|
329
|
+
(args) => controlApi.sendDingtalk(args || {})
|
|
350
330
|
);
|
|
351
331
|
|
|
352
332
|
tryRegisterTool(
|
|
@@ -432,8 +412,6 @@ const plugin = {
|
|
|
432
412
|
api?.config?.plugins?.glanceBridge?.config ||
|
|
433
413
|
{};
|
|
434
414
|
|
|
435
|
-
const contactsStorePath = resolveContactsStorePath({ pluginConfig });
|
|
436
|
-
|
|
437
415
|
const startupPromise = startPluginRuntime({
|
|
438
416
|
runtime: api?.runtime,
|
|
439
417
|
pluginConfig
|
|
@@ -442,7 +420,7 @@ const plugin = {
|
|
|
442
420
|
api?.runtime?.logger?.error?.(`[openclaw-glance-plugin] runtime start failed: ${err.message}`);
|
|
443
421
|
});
|
|
444
422
|
|
|
445
|
-
const controlApi = buildControlApi(startupPromise
|
|
423
|
+
const controlApi = buildControlApi(startupPromise);
|
|
446
424
|
api.glanceBridge = controlApi;
|
|
447
425
|
registerControlTools(api, controlApi);
|
|
448
426
|
|