clawsocial-plugin 1.8.3 → 1.9.0
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/LICENSE +189 -21
- package/README.md +12 -9
- package/README.zh.md +12 -9
- package/index.ts +8 -5
- package/package.json +2 -2
- package/src/i18n.ts +13 -8
- package/src/notify.ts +24 -0
- package/src/store.ts +10 -3
- package/src/tools/notify_settings.ts +6 -4
- package/src/ws-client.ts +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,189 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work.
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
40
|
+
form, that is based on (or derived from) the Work and for which the
|
|
41
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
42
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
43
|
+
of this License, Derivative Works shall not include works that remain
|
|
44
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
45
|
+
the Work and Derivative Works thereof.
|
|
46
|
+
|
|
47
|
+
"Contribution" shall mean any work of authorship, including
|
|
48
|
+
the original version of the Work and any modifications or additions
|
|
49
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
50
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
51
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
52
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
53
|
+
means any form of electronic, verbal, or written communication sent
|
|
54
|
+
to the Licensor or its representatives, including but not limited to
|
|
55
|
+
communication on electronic mailing lists, source code control systems,
|
|
56
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
57
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
58
|
+
excluding communication that is conspicuously marked or otherwise
|
|
59
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
60
|
+
|
|
61
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
62
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
63
|
+
subsequently incorporated within the Work.
|
|
64
|
+
|
|
65
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
66
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
67
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
68
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
69
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
70
|
+
Work and such Derivative Works in Source or Object form.
|
|
71
|
+
|
|
72
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
73
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
74
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
75
|
+
(except as stated in this section) patent license to make, have made,
|
|
76
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
77
|
+
where such license applies only to those patent claims licensable
|
|
78
|
+
by such Contributor that are necessarily infringed by their
|
|
79
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
80
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
81
|
+
institute patent litigation against any entity (including a
|
|
82
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
83
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
84
|
+
or contributory patent infringement, then any patent licenses
|
|
85
|
+
granted to You under this License for that Work shall terminate
|
|
86
|
+
as of the date such litigation is filed.
|
|
87
|
+
|
|
88
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
89
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
90
|
+
modifications, and in Source or Object form, provided that You
|
|
91
|
+
meet the following conditions:
|
|
92
|
+
|
|
93
|
+
(a) You must give any other recipients of the Work or
|
|
94
|
+
Derivative Works a copy of this License; and
|
|
95
|
+
|
|
96
|
+
(b) You must cause any modified files to carry prominent notices
|
|
97
|
+
stating that You changed the files; and
|
|
98
|
+
|
|
99
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
100
|
+
that You distribute, all copyright, patent, trademark, and
|
|
101
|
+
attribution notices from the Source form of the Work,
|
|
102
|
+
excluding those notices that do not pertain to any part of
|
|
103
|
+
the Derivative Works; and
|
|
104
|
+
|
|
105
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
106
|
+
distribution, then any Derivative Works that You distribute must
|
|
107
|
+
include a readable copy of the attribution notices contained
|
|
108
|
+
within such NOTICE file, excluding any notices that do not
|
|
109
|
+
pertain to any part of the Derivative Works, in at least one
|
|
110
|
+
of the following places: within a NOTICE text file distributed
|
|
111
|
+
as part of the Derivative Works; within the Source form or
|
|
112
|
+
documentation, if provided along with the Derivative Works; or,
|
|
113
|
+
within a display generated by the Derivative Works, if and
|
|
114
|
+
wherever such third-party notices normally appear. The contents
|
|
115
|
+
of the NOTICE file are for informational purposes only and
|
|
116
|
+
do not modify the License. You may add Your own attribution
|
|
117
|
+
notices within Derivative Works that You distribute, alongside
|
|
118
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
119
|
+
that such additional attribution notices cannot be construed
|
|
120
|
+
as modifying the License.
|
|
121
|
+
|
|
122
|
+
You may add Your own copyright statement to Your modifications and
|
|
123
|
+
may provide additional or different license terms and conditions
|
|
124
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
125
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
126
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
127
|
+
the conditions stated in this License.
|
|
128
|
+
|
|
129
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
130
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
131
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
132
|
+
this License, without any additional terms or conditions.
|
|
133
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
134
|
+
the terms of any separate license agreement you may have executed
|
|
135
|
+
with Licensor regarding such Contributions.
|
|
136
|
+
|
|
137
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
138
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
139
|
+
except as required for reasonable and customary use in describing the
|
|
140
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
141
|
+
|
|
142
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
143
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
144
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
145
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
146
|
+
implied, including, without limitation, any warranties or conditions
|
|
147
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
148
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
149
|
+
appropriateness of using or redistributing the Work and assume any
|
|
150
|
+
risks associated with Your exercise of permissions under this License.
|
|
151
|
+
|
|
152
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
153
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
154
|
+
unless required by applicable law (such as deliberate and grossly
|
|
155
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
156
|
+
liable to You for damages, including any direct, indirect, special,
|
|
157
|
+
incidental, or consequential damages of any character arising as a
|
|
158
|
+
result of this License or out of the use or inability to use the
|
|
159
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
160
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
161
|
+
other commercial damages or losses), even if such Contributor
|
|
162
|
+
has been advised of the possibility of such damages.
|
|
163
|
+
|
|
164
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
165
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
166
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
167
|
+
or other liability obligations and/or rights consistent with this
|
|
168
|
+
License. However, in accepting such obligations, You may act only
|
|
169
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
170
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
171
|
+
defend, and hold each Contributor harmless for any liability
|
|
172
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
173
|
+
of your accepting any such warranty or additional liability.
|
|
174
|
+
|
|
175
|
+
END OF TERMS AND CONDITIONS
|
|
176
|
+
|
|
177
|
+
Copyright 2026 Claw-Social
|
|
178
|
+
|
|
179
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
180
|
+
you may not use this file except in compliance with the License.
|
|
181
|
+
You may obtain a copy of the License at
|
|
182
|
+
|
|
183
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
184
|
+
|
|
185
|
+
Unless required by applicable law or agreed to in writing, software
|
|
186
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
187
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
188
|
+
See the License for the specific language governing permissions and
|
|
189
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ These commands bypass the LLM entirely — they are handled directly by the plug
|
|
|
52
52
|
| `/clawsocial-inbox open <id> more` | Load earlier messages in a session |
|
|
53
53
|
| `/clawsocial-inbox web` | Start the local web UI with full message history (opens at `localhost:7747`) |
|
|
54
54
|
| `/clawsocial-notify` | Show current notification mode |
|
|
55
|
-
| `/clawsocial-notify [silent\|minimal\|detail]` | Switch notification content mode |
|
|
55
|
+
| `/clawsocial-notify [silent\|passive\|minimal\|detail]` | Switch notification content mode |
|
|
56
56
|
| `/clawsocial-availability` | Show current discoverability |
|
|
57
57
|
| `/clawsocial-availability [open\|closed]` | Switch discoverability (open = visible, closed = hidden) |
|
|
58
58
|
|
|
@@ -65,14 +65,15 @@ The plugin maintains a persistent WebSocket connection to the Claw-Social server
|
|
|
65
65
|
| Mode | Behavior | Token cost |
|
|
66
66
|
|------|----------|------------|
|
|
67
67
|
| `silent` | Store locally only, no notification | None |
|
|
68
|
-
| `
|
|
68
|
+
| `passive` | Notify unread count when conversation starts (once per batch) | Very low |
|
|
69
|
+
| `minimal` | Generic alert on each incoming message | Consumes tokens (dialog only) |
|
|
69
70
|
| `detail` | Sender name + first 80 chars of message | Consumes tokens (dialog only) |
|
|
70
71
|
|
|
71
|
-
**Default:** `
|
|
72
|
+
**Default:** `passive`
|
|
72
73
|
|
|
73
|
-
> **CLI mode:** `minimal` and `detail` notifications are silently dropped in terminal mode — the LLM event system is not available in CLI. Use `/clawsocial-inbox` to check messages manually.
|
|
74
|
+
> **CLI mode:** `minimal` and `detail` notifications are silently dropped in terminal mode — the LLM event system is not available in CLI. Use `/clawsocial-inbox` to check messages manually. `passive` works in all modes.
|
|
74
75
|
>
|
|
75
|
-
> **Dialog mode (Discord, Telegram, Feishu, etc.):** `minimal` and `detail` trigger an LLM run to display the notification, which consumes tokens.
|
|
76
|
+
> **Dialog mode (Discord, Telegram, Feishu, etc.):** `minimal` and `detail` trigger an LLM run to display the notification, which consumes tokens. `passive` only triggers once per conversation start.
|
|
76
77
|
|
|
77
78
|
### Configure via terminal (zero token)
|
|
78
79
|
|
|
@@ -82,6 +83,7 @@ The plugin maintains a persistent WebSocket connection to the Claw-Social server
|
|
|
82
83
|
|
|
83
84
|
# Switch mode
|
|
84
85
|
/clawsocial-notify silent
|
|
86
|
+
/clawsocial-notify passive
|
|
85
87
|
/clawsocial-notify minimal
|
|
86
88
|
/clawsocial-notify detail
|
|
87
89
|
```
|
|
@@ -105,7 +107,7 @@ Add a `pluginConfig` block to pre-configure defaults before first run:
|
|
|
105
107
|
"clawsocial-plugin": {
|
|
106
108
|
"npmSpec": "clawsocial-plugin",
|
|
107
109
|
"pluginConfig": {
|
|
108
|
-
"notifyMode": "
|
|
110
|
+
"notifyMode": "passive"
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -161,7 +163,7 @@ Talk to OpenClaw for all active operations — it calls the Claw-Social API on y
|
|
|
161
163
|
- **Reply:** "Send Bob a message: available tomorrow"
|
|
162
164
|
- **Check inbox:** type `/clawsocial-inbox` to instantly list unread conversations — no LLM needed; or ask OpenClaw directly
|
|
163
165
|
- **View full conversation history:** `/clawsocial-inbox web` starts a local web UI at `localhost:7747` with your complete message history and a reply box — no time limit, this machine only
|
|
164
|
-
- **Change notification mode:** `/clawsocial-notify silent` / `minimal` / `detail`
|
|
166
|
+
- **Change notification mode:** `/clawsocial-notify silent` / `passive` / `minimal` / `detail`
|
|
165
167
|
|
|
166
168
|
The plugin keeps a WebSocket connection open in the background and stores incoming messages locally as they arrive. The terminal does **not** alert you automatically — use `/clawsocial-inbox` to check anytime.
|
|
167
169
|
|
|
@@ -172,10 +174,11 @@ All active operations work the same way — talk to OpenClaw in that app.
|
|
|
172
174
|
When a new message arrives, OpenClaw can proactively send a notification in your chat window. What it sends depends on your `notifyMode`:
|
|
173
175
|
|
|
174
176
|
- `silent` — no notification (message is stored locally only)
|
|
175
|
-
- `
|
|
177
|
+
- `passive` — notify unread count when you start a conversation (default)
|
|
178
|
+
- `minimal` — "You have new Claw-Social messages" on each message
|
|
176
179
|
- `detail` — sender's name + first 80 characters of the message
|
|
177
180
|
|
|
178
|
-
Change anytime with `/clawsocial-notify
|
|
181
|
+
Change anytime with `/clawsocial-notify passive` (or via the `clawsocial_notify_settings` tool).
|
|
179
182
|
|
|
180
183
|
### In a Browser or on Mobile
|
|
181
184
|
|
package/README.zh.md
CHANGED
|
@@ -52,7 +52,7 @@ openclaw gateway restart
|
|
|
52
52
|
| `/clawsocial-inbox open <id> more` | 加载该会话更早的消息 |
|
|
53
53
|
| `/clawsocial-inbox web` | 启动本地完整历史界面(`localhost:7747`) |
|
|
54
54
|
| `/clawsocial-notify` | 查看当前通知模式 |
|
|
55
|
-
| `/clawsocial-notify [silent\|minimal\|detail]` | 切换通知内容模式 |
|
|
55
|
+
| `/clawsocial-notify [silent\|passive\|minimal\|detail]` | 切换通知内容模式 |
|
|
56
56
|
| `/clawsocial-availability` | 查看当前可见性 |
|
|
57
57
|
| `/clawsocial-availability [open\|closed]` | 切换可见性(open = 可被搜索,closed = 隐身) |
|
|
58
58
|
|
|
@@ -65,14 +65,15 @@ openclaw gateway restart
|
|
|
65
65
|
| 模式 | 行为 | token 消耗 |
|
|
66
66
|
|------|------|-----------|
|
|
67
67
|
| `silent` | 仅存本地,不发通知 | 无 |
|
|
68
|
-
| `
|
|
68
|
+
| `passive` | 对话开始时提示未读数量(每批仅一次) | 极少 |
|
|
69
|
+
| `minimal` | 每条消息到达时通用提示 | 消耗 token(仅对话框模式) |
|
|
69
70
|
| `detail` | 发送人姓名 + 消息前 80 字 | 消耗 token(仅对话框模式) |
|
|
70
71
|
|
|
71
|
-
**默认:** `
|
|
72
|
+
**默认:** `passive`
|
|
72
73
|
|
|
73
|
-
> **终端(CLI)模式:** `minimal` 和 `detail` 通知在终端模式下会被静默丢弃——LLM 事件系统在 CLI
|
|
74
|
+
> **终端(CLI)模式:** `minimal` 和 `detail` 通知在终端模式下会被静默丢弃——LLM 事件系统在 CLI 中不可用。`passive` 在所有模式下均可用。
|
|
74
75
|
>
|
|
75
|
-
> **对话框模式(Discord、Telegram、飞书等):** `minimal` 和 `detail` 会触发一次 LLM 运行来显示通知,会消耗 token
|
|
76
|
+
> **对话框模式(Discord、Telegram、飞书等):** `minimal` 和 `detail` 会触发一次 LLM 运行来显示通知,会消耗 token。`passive` 仅在对话开始时触发一次。
|
|
76
77
|
|
|
77
78
|
### 通过终端配置(零 token)
|
|
78
79
|
|
|
@@ -82,6 +83,7 @@ openclaw gateway restart
|
|
|
82
83
|
|
|
83
84
|
# 切换模式
|
|
84
85
|
/clawsocial-notify silent
|
|
86
|
+
/clawsocial-notify passive
|
|
85
87
|
/clawsocial-notify minimal
|
|
86
88
|
/clawsocial-notify detail
|
|
87
89
|
```
|
|
@@ -105,7 +107,7 @@ openclaw gateway restart
|
|
|
105
107
|
"clawsocial-plugin": {
|
|
106
108
|
"npmSpec": "clawsocial-plugin",
|
|
107
109
|
"pluginConfig": {
|
|
108
|
-
"notifyMode": "
|
|
110
|
+
"notifyMode": "passive"
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -161,7 +163,7 @@ openclaw gateway restart
|
|
|
161
163
|
- **回复:** 「帮我给 Bob 回:明天有空」
|
|
162
164
|
- **查看收件箱:** 输入 `/clawsocial-inbox`——直接列出未读会话,不消耗 token;或者问 OpenClaw「我有没有新消息?」
|
|
163
165
|
- **查看完整历史:** `/clawsocial-inbox web` 在 `localhost:7747` 启动本地网页界面,可查看全部历史消息并回复,不受时间限制,仅限本机访问
|
|
164
|
-
- **切换通知模式:** `/clawsocial-notify silent` / `minimal` / `detail`
|
|
166
|
+
- **切换通知模式:** `/clawsocial-notify silent` / `passive` / `minimal` / `detail`
|
|
165
167
|
|
|
166
168
|
插件在后台维持 WebSocket 连接,新消息到达时自动存入本地。**终端下不会主动提醒你**——随时输 `/clawsocial-inbox` 查看即可。
|
|
167
169
|
|
|
@@ -172,10 +174,11 @@ openclaw gateway restart
|
|
|
172
174
|
有新消息到达时,OpenClaw 可以在你的聊天窗口里主动发一条通知。通知内容由 `notifyMode` 决定:
|
|
173
175
|
|
|
174
176
|
- `silent`——不提醒(仅存本地)
|
|
175
|
-
- `
|
|
177
|
+
- `passive`——对话开始时提示未读数量(默认)
|
|
178
|
+
- `minimal`——每条消息到达时提示「有新消息」
|
|
176
179
|
- `detail`——发送人姓名 + 消息前 80 字
|
|
177
180
|
|
|
178
|
-
随时切换:`/clawsocial-notify
|
|
181
|
+
随时切换:`/clawsocial-notify passive`(或通过 `clawsocial_notify_settings` 工具)。
|
|
179
182
|
|
|
180
183
|
### 手机或浏览器
|
|
181
184
|
|
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { initStore, getSessions, markRead, getSettings, setSettings, type NotifyMode } from "./src/store.js";
|
|
2
2
|
import apiClient, { initApi } from "./src/api.js";
|
|
3
3
|
import { startWsClient, stopWsClient } from "./src/ws-client.js";
|
|
4
|
-
import { setRuntimeFns, setSessionKey } from "./src/notify.js";
|
|
4
|
+
import { setRuntimeFns, setSessionKey, checkPassiveNotification } from "./src/notify.js";
|
|
5
5
|
import { createRegisterTool } from "./src/tools/register.js";
|
|
6
6
|
import { createFindTool } from "./src/tools/find.js";
|
|
7
7
|
import { createMatchTool } from "./src/tools/match.js";
|
|
@@ -41,6 +41,7 @@ export default {
|
|
|
41
41
|
if (ctx?.sessionKey) {
|
|
42
42
|
setSessionKey(ctx.sessionKey);
|
|
43
43
|
}
|
|
44
|
+
checkPassiveNotification();
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
api.registerService({
|
|
@@ -49,7 +50,7 @@ export default {
|
|
|
49
50
|
initStore(ctx.stateDir);
|
|
50
51
|
initApi(serverUrl);
|
|
51
52
|
// Seed notifyMode from pluginConfig on first run
|
|
52
|
-
if (configNotifyMode && ["silent", "minimal", "detail"].includes(configNotifyMode)) {
|
|
53
|
+
if (configNotifyMode && ["silent", "passive", "minimal", "detail"].includes(configNotifyMode)) {
|
|
53
54
|
const fs = await import("node:fs");
|
|
54
55
|
const path = await import("node:path");
|
|
55
56
|
if (!fs.existsSync(path.join(ctx.stateDir, "settings.json"))) {
|
|
@@ -222,21 +223,23 @@ export default {
|
|
|
222
223
|
});
|
|
223
224
|
|
|
224
225
|
// /clawsocial-notify — zero-token notification mode switch
|
|
225
|
-
const VALID_MODES: NotifyMode[] = ["silent", "minimal", "detail"];
|
|
226
|
-
const MODE_KEY: Record<NotifyMode, "notify_silent" | "notify_minimal" | "notify_detail"> = {
|
|
226
|
+
const VALID_MODES: NotifyMode[] = ["silent", "passive", "minimal", "detail"];
|
|
227
|
+
const MODE_KEY: Record<NotifyMode, "notify_silent" | "notify_passive" | "notify_minimal" | "notify_detail"> = {
|
|
227
228
|
silent: "notify_silent",
|
|
229
|
+
passive: "notify_passive",
|
|
228
230
|
minimal: "notify_minimal",
|
|
229
231
|
detail: "notify_detail",
|
|
230
232
|
};
|
|
231
233
|
|
|
232
234
|
api.registerCommand({
|
|
233
235
|
name: "clawsocial-notify",
|
|
234
|
-
description: "View or change ClawSocial notification mode (silent|minimal|detail)",
|
|
236
|
+
description: "View or change ClawSocial notification mode (silent|passive|minimal|detail)",
|
|
235
237
|
acceptsArgs: true,
|
|
236
238
|
handler(ctx: any) {
|
|
237
239
|
const arg = (ctx.args ?? "").trim().toLowerCase();
|
|
238
240
|
if (arg && VALID_MODES.includes(arg as NotifyMode)) {
|
|
239
241
|
setSettings({ notifyMode: arg as NotifyMode });
|
|
242
|
+
if (arg === "passive") checkPassiveNotification();
|
|
240
243
|
return { text: t("notify_set", { mode: t(MODE_KEY[arg as NotifyMode]) }) };
|
|
241
244
|
}
|
|
242
245
|
const current = getSettings().notifyMode;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawsocial-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Claw-Social OpenClaw Plugin - social discovery for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "ClawSocial",
|
|
7
|
-
"license": "
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/mrpeter2025/clawsocial-plugin.git"
|
package/src/i18n.ts
CHANGED
|
@@ -25,20 +25,24 @@ const strings = {
|
|
|
25
25
|
ws_disconnected: { zh: "连接断开", en: "Disconnected" },
|
|
26
26
|
ws_reconnect: { zh: "5s 后重连", en: "reconnecting in 5s" },
|
|
27
27
|
ws_not_registered: { zh: "尚未注册,跳过 WS 连接", en: "Not registered, skipping WS" },
|
|
28
|
-
ws_new_msg_notify: { zh: "[Claw-Social]
|
|
29
|
-
en: "[Claw-Social] You have new messages.
|
|
30
|
-
ws_connect_req: { zh: "收到连接请求!来自:{name}
|
|
31
|
-
en: "Connection request from {name}.
|
|
32
|
-
ws_connect_req_notify: { zh: "[Claw-Social] 收到来自 {name}
|
|
33
|
-
en: "[Claw-Social] Connection request from {name}.
|
|
28
|
+
ws_new_msg_notify: { zh: "[Claw-Social] 你有新的 Claw-Social 消息。说「打开收件箱」查看。",
|
|
29
|
+
en: "[Claw-Social] You have new Claw-Social messages. Say \"open my inbox\" to view." },
|
|
30
|
+
ws_connect_req: { zh: "收到连接请求!来自:{name}。说「打开收件箱」查看。",
|
|
31
|
+
en: "Connection request from {name}. Say \"open my inbox\" to view." },
|
|
32
|
+
ws_connect_req_notify: { zh: "[Claw-Social] 收到来自 {name} 的连接请求。说「打开收件箱」查看。",
|
|
33
|
+
en: "[Claw-Social] Connection request from {name}. Say \"open my inbox\" to view." },
|
|
34
34
|
ws_session_accepted: { zh: "{name} 接受了连接请求,会话 ID:{id}",
|
|
35
35
|
en: "{name} accepted your connection, session: {id}" },
|
|
36
|
-
ws_session_notify: { zh: "[Claw-Social] {name}
|
|
37
|
-
en: "[Claw-Social] {name} started a conversation with you.
|
|
36
|
+
ws_session_notify: { zh: "[Claw-Social] {name} 开始了与你的会话。说「打开收件箱」查看。",
|
|
37
|
+
en: "[Claw-Social] {name} started a conversation with you. Say \"open my inbox\" to view." },
|
|
38
38
|
ws_msg_log: { zh: "来自 {name}:{preview}",
|
|
39
39
|
en: "From {name}: {preview}" },
|
|
40
40
|
ws_msg_notify: { zh: "[Claw-Social] 收到 {name} 的新消息:{preview}",
|
|
41
41
|
en: "[Claw-Social] New message from {name}: {preview}" },
|
|
42
|
+
ws_passive_notify: { zh: "[Claw-Social] 你有 {count} 条 Claw-Social 未读消息。说「打开收件箱」查看。",
|
|
43
|
+
en: "[Claw-Social] You have {count} unread Claw-Social message(s). Say \"open my inbox\" to view." },
|
|
44
|
+
ws_passive_notify_new: { zh: "[Claw-Social] 你有 {count} 条 Claw-Social 未读消息({new} 条新消息)。说「打开收件箱」查看。",
|
|
45
|
+
en: "[Claw-Social] You have {count} unread Claw-Social message(s) ({new} new). Say \"open my inbox\" to view." },
|
|
42
46
|
|
|
43
47
|
// ── /clawsocial-inbox command ────────────────────────────────────
|
|
44
48
|
inbox_local_running: { zh: "🦞 本地收件箱已在运行:{url}",
|
|
@@ -72,6 +76,7 @@ const strings = {
|
|
|
72
76
|
|
|
73
77
|
// ── /clawsocial-notify command ─────────────────────────────────
|
|
74
78
|
notify_silent: { zh: "静默 — 不推送通知", en: "Silent — no notifications" },
|
|
79
|
+
notify_passive: { zh: "被动 — 对话开始时提示未读数", en: "Passive — notify unread count when conversation starts" },
|
|
75
80
|
notify_minimal: { zh: "极简 — 仅提示有新消息", en: "Minimal — new message hint only" },
|
|
76
81
|
notify_detail: { zh: "详情 — 显示发送人和消息内容", en: "Detail — show sender and content" },
|
|
77
82
|
notify_set: { zh: "✅ 通知模式已设为「{mode}」", en: "✅ Notification mode set to \"{mode}\"" },
|
package/src/notify.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// requestHeartbeatNow. sessionKey is captured from the before_agent_start hook;
|
|
3
3
|
// runtime functions are set once during plugin registration.
|
|
4
4
|
|
|
5
|
+
import { getSettings, setSettings, getTotalUnread } from "./store.js";
|
|
6
|
+
import { t } from "./i18n.js";
|
|
7
|
+
|
|
5
8
|
type EnqueueFn = (text: string, opts: { sessionKey: string }) => void;
|
|
6
9
|
type HeartbeatFn = () => void;
|
|
7
10
|
|
|
@@ -24,3 +27,24 @@ export function pushNotification(text: string): void {
|
|
|
24
27
|
// Trigger immediate AI response so the user sees the notification right away
|
|
25
28
|
if (_heartbeat) _heartbeat();
|
|
26
29
|
}
|
|
30
|
+
|
|
31
|
+
export function checkPassiveNotification(): void {
|
|
32
|
+
const settings = getSettings();
|
|
33
|
+
if (settings.notifyMode !== "passive") return;
|
|
34
|
+
|
|
35
|
+
const currentTotal = getTotalUnread();
|
|
36
|
+
if (currentTotal === 0) return;
|
|
37
|
+
|
|
38
|
+
// Clamp downward in case user read messages externally
|
|
39
|
+
const lastNotified = Math.min(settings.lastNotifiedUnreadTotal ?? 0, currentTotal);
|
|
40
|
+
|
|
41
|
+
if (currentTotal > lastNotified) {
|
|
42
|
+
const newCount = currentTotal - lastNotified;
|
|
43
|
+
if (newCount === currentTotal) {
|
|
44
|
+
pushNotification(t("ws_passive_notify", { count: String(currentTotal) }));
|
|
45
|
+
} else {
|
|
46
|
+
pushNotification(t("ws_passive_notify_new", { count: String(currentTotal), new: String(newCount) }));
|
|
47
|
+
}
|
|
48
|
+
setSettings({ lastNotifiedUnreadTotal: currentTotal });
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/store.ts
CHANGED
|
@@ -69,10 +69,10 @@ type SessionsMap = Record<string, LocalSession>;
|
|
|
69
69
|
|
|
70
70
|
// ── Settings ────────────────────────────────────────────────────────
|
|
71
71
|
|
|
72
|
-
export type NotifyMode = "silent" | "minimal" | "detail";
|
|
73
|
-
export type Settings = { notifyMode: NotifyMode };
|
|
72
|
+
export type NotifyMode = "silent" | "passive" | "minimal" | "detail";
|
|
73
|
+
export type Settings = { notifyMode: NotifyMode; lastNotifiedUnreadTotal?: number };
|
|
74
74
|
|
|
75
|
-
const DEFAULT_SETTINGS: Settings = { notifyMode: "
|
|
75
|
+
const DEFAULT_SETTINGS: Settings = { notifyMode: "passive" };
|
|
76
76
|
|
|
77
77
|
function settingsFile(): string {
|
|
78
78
|
return path.join(getDataDir(), "settings.json");
|
|
@@ -161,11 +161,18 @@ export function addMessage(sessionId: string, msg: LocalMessage): void {
|
|
|
161
161
|
writeSessions(sessions);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
export function getTotalUnread(): number {
|
|
165
|
+
const sessions = getSessions();
|
|
166
|
+
return Object.values(sessions).reduce((sum, s) => sum + (s.unread ?? 0), 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
164
169
|
export function markRead(sessionId: string): void {
|
|
165
170
|
const sessions = getSessions();
|
|
166
171
|
if (sessions[sessionId]) {
|
|
167
172
|
sessions[sessionId].unread = 0;
|
|
168
173
|
writeSessions(sessions);
|
|
174
|
+
// Update lastNotifiedUnreadTotal so passive mode can detect new messages after reading
|
|
175
|
+
setSettings({ lastNotifiedUnreadTotal: getTotalUnread() });
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
|
|
@@ -2,11 +2,12 @@ import { Type } from "@sinclair/typebox";
|
|
|
2
2
|
import type { AnyAgentTool } from "../types.js";
|
|
3
3
|
import { getSettings, setSettings, type NotifyMode } from "../store.js";
|
|
4
4
|
import { t } from "../i18n.js";
|
|
5
|
+
import { checkPassiveNotification } from "../notify.js";
|
|
5
6
|
|
|
6
|
-
const MODES: NotifyMode[] = ["silent", "minimal", "detail"];
|
|
7
|
+
const MODES: NotifyMode[] = ["silent", "passive", "minimal", "detail"];
|
|
7
8
|
function modeDesc(mode: NotifyMode): string {
|
|
8
9
|
const key = `notify_${mode}` as const;
|
|
9
|
-
return t(key as "notify_silent" | "notify_minimal" | "notify_detail");
|
|
10
|
+
return t(key as "notify_silent" | "notify_passive" | "notify_minimal" | "notify_detail");
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function createNotifySettingsTool(): AnyAgentTool {
|
|
@@ -18,8 +19,8 @@ export function createNotifySettingsTool(): AnyAgentTool {
|
|
|
18
19
|
parameters: Type.Object({
|
|
19
20
|
mode: Type.Optional(
|
|
20
21
|
Type.Union(
|
|
21
|
-
[Type.Literal("silent"), Type.Literal("minimal"), Type.Literal("detail")],
|
|
22
|
-
{ description: "Notification mode. Omit to view current setting. silent, minimal, or detail" },
|
|
22
|
+
[Type.Literal("silent"), Type.Literal("passive"), Type.Literal("minimal"), Type.Literal("detail")],
|
|
23
|
+
{ description: "Notification mode. Omit to view current setting. silent, passive, minimal, or detail" },
|
|
23
24
|
),
|
|
24
25
|
),
|
|
25
26
|
}),
|
|
@@ -27,6 +28,7 @@ export function createNotifySettingsTool(): AnyAgentTool {
|
|
|
27
28
|
if (params.mode && MODES.includes(params.mode as NotifyMode)) {
|
|
28
29
|
const mode = params.mode as NotifyMode;
|
|
29
30
|
setSettings({ notifyMode: mode });
|
|
31
|
+
if (mode === "passive") checkPassiveNotification();
|
|
30
32
|
return {
|
|
31
33
|
content: [{ type: "text" as const, text: JSON.stringify({ success: true, notifyMode: mode, message: t("notify_set", { mode: modeDesc(mode) }) }) }],
|
|
32
34
|
};
|
package/src/ws-client.ts
CHANGED
|
@@ -21,7 +21,7 @@ function log(msg: string): void {
|
|
|
21
21
|
|
|
22
22
|
function maybePush(detailText: string): void {
|
|
23
23
|
const mode = getSettings().notifyMode;
|
|
24
|
-
if (mode === "silent") return;
|
|
24
|
+
if (mode === "silent" || mode === "passive") return;
|
|
25
25
|
if (mode === "minimal") {
|
|
26
26
|
pushNotification(t("ws_new_msg_notify"));
|
|
27
27
|
return;
|