openclaw-event-server-plugin 1.0.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.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +473 -0
  3. package/config.example.json +203 -0
  4. package/dist/broadcast/index.d.ts +3 -0
  5. package/dist/broadcast/index.d.ts.map +1 -0
  6. package/dist/broadcast/index.js +19 -0
  7. package/dist/broadcast/index.js.map +1 -0
  8. package/dist/broadcast/queue.d.ts +63 -0
  9. package/dist/broadcast/queue.d.ts.map +1 -0
  10. package/dist/broadcast/queue.js +259 -0
  11. package/dist/broadcast/queue.js.map +1 -0
  12. package/dist/broadcast/webhook.d.ts +30 -0
  13. package/dist/broadcast/webhook.d.ts.map +1 -0
  14. package/dist/broadcast/webhook.js +184 -0
  15. package/dist/broadcast/webhook.js.map +1 -0
  16. package/dist/broadcast/websocketServer.d.ts +90 -0
  17. package/dist/broadcast/websocketServer.d.ts.map +1 -0
  18. package/dist/broadcast/websocketServer.js +403 -0
  19. package/dist/broadcast/websocketServer.js.map +1 -0
  20. package/dist/config/config-types.d.ts +280 -0
  21. package/dist/config/config-types.d.ts.map +1 -0
  22. package/dist/config/config-types.js +3 -0
  23. package/dist/config/config-types.js.map +1 -0
  24. package/dist/config/default-config.d.ts +3 -0
  25. package/dist/config/default-config.d.ts.map +1 -0
  26. package/dist/config/default-config.js +136 -0
  27. package/dist/config/default-config.js.map +1 -0
  28. package/dist/config/env.d.ts +6 -0
  29. package/dist/config/env.d.ts.map +1 -0
  30. package/dist/config/env.js +160 -0
  31. package/dist/config/env.js.map +1 -0
  32. package/dist/config/event-types.d.ts +5 -0
  33. package/dist/config/event-types.d.ts.map +1 -0
  34. package/dist/config/event-types.js +54 -0
  35. package/dist/config/event-types.js.map +1 -0
  36. package/dist/config/handler.d.ts +9 -0
  37. package/dist/config/handler.d.ts.map +1 -0
  38. package/dist/config/handler.js +17 -0
  39. package/dist/config/handler.js.map +1 -0
  40. package/dist/config/helpers.d.ts +5 -0
  41. package/dist/config/helpers.d.ts.map +1 -0
  42. package/dist/config/helpers.js +40 -0
  43. package/dist/config/helpers.js.map +1 -0
  44. package/dist/config/index.d.ts +3 -0
  45. package/dist/config/index.d.ts.map +1 -0
  46. package/dist/config/index.js +19 -0
  47. package/dist/config/index.js.map +1 -0
  48. package/dist/config/merge.d.ts +6 -0
  49. package/dist/config/merge.d.ts.map +1 -0
  50. package/dist/config/merge.js +108 -0
  51. package/dist/config/merge.js.map +1 -0
  52. package/dist/config/schema-hook-bridge.d.ts +594 -0
  53. package/dist/config/schema-hook-bridge.d.ts.map +1 -0
  54. package/dist/config/schema-hook-bridge.js +423 -0
  55. package/dist/config/schema-hook-bridge.js.map +1 -0
  56. package/dist/config/schema.d.ts +915 -0
  57. package/dist/config/schema.d.ts.map +1 -0
  58. package/dist/config/schema.js +323 -0
  59. package/dist/config/schema.js.map +1 -0
  60. package/dist/config/types.d.ts +9 -0
  61. package/dist/config/types.d.ts.map +1 -0
  62. package/dist/config/types.js +28 -0
  63. package/dist/config/types.js.map +1 -0
  64. package/dist/config/validate-hook-bridge.d.ts +3 -0
  65. package/dist/config/validate-hook-bridge.d.ts.map +1 -0
  66. package/dist/config/validate-hook-bridge.js +246 -0
  67. package/dist/config/validate-hook-bridge.js.map +1 -0
  68. package/dist/config/validate.d.ts +17 -0
  69. package/dist/config/validate.d.ts.map +1 -0
  70. package/dist/config/validate.js +194 -0
  71. package/dist/config/validate.js.map +1 -0
  72. package/dist/events/index.d.ts +4 -0
  73. package/dist/events/index.d.ts.map +1 -0
  74. package/dist/events/index.js +20 -0
  75. package/dist/events/index.js.map +1 -0
  76. package/dist/events/redaction.d.ts +5 -0
  77. package/dist/events/redaction.d.ts.map +1 -0
  78. package/dist/events/redaction.js +46 -0
  79. package/dist/events/redaction.js.map +1 -0
  80. package/dist/events/signing.d.ts +8 -0
  81. package/dist/events/signing.d.ts.map +1 -0
  82. package/dist/events/signing.js +35 -0
  83. package/dist/events/signing.js.map +1 -0
  84. package/dist/events/types.d.ts +125 -0
  85. package/dist/events/types.d.ts.map +1 -0
  86. package/dist/events/types.js +9 -0
  87. package/dist/events/types.js.map +1 -0
  88. package/dist/hooks/agent-hooks.d.ts +59 -0
  89. package/dist/hooks/agent-hooks.d.ts.map +1 -0
  90. package/dist/hooks/agent-hooks.js +116 -0
  91. package/dist/hooks/agent-hooks.js.map +1 -0
  92. package/dist/hooks/command-hooks.d.ts +13 -0
  93. package/dist/hooks/command-hooks.d.ts.map +1 -0
  94. package/dist/hooks/command-hooks.js +26 -0
  95. package/dist/hooks/command-hooks.js.map +1 -0
  96. package/dist/hooks/event-factory.d.ts +21 -0
  97. package/dist/hooks/event-factory.d.ts.map +1 -0
  98. package/dist/hooks/event-factory.js +31 -0
  99. package/dist/hooks/event-factory.js.map +1 -0
  100. package/dist/hooks/gateway-hooks.d.ts +14 -0
  101. package/dist/hooks/gateway-hooks.d.ts.map +1 -0
  102. package/dist/hooks/gateway-hooks.js +43 -0
  103. package/dist/hooks/gateway-hooks.js.map +1 -0
  104. package/dist/hooks/index.d.ts +10 -0
  105. package/dist/hooks/index.d.ts.map +1 -0
  106. package/dist/hooks/index.js +26 -0
  107. package/dist/hooks/index.js.map +1 -0
  108. package/dist/hooks/message-hooks.d.ts +33 -0
  109. package/dist/hooks/message-hooks.d.ts.map +1 -0
  110. package/dist/hooks/message-hooks.js +208 -0
  111. package/dist/hooks/message-hooks.js.map +1 -0
  112. package/dist/hooks/session-hooks.d.ts +89 -0
  113. package/dist/hooks/session-hooks.d.ts.map +1 -0
  114. package/dist/hooks/session-hooks.js +253 -0
  115. package/dist/hooks/session-hooks.js.map +1 -0
  116. package/dist/hooks/status-reducer.d.ts +30 -0
  117. package/dist/hooks/status-reducer.d.ts.map +1 -0
  118. package/dist/hooks/status-reducer.js +157 -0
  119. package/dist/hooks/status-reducer.js.map +1 -0
  120. package/dist/hooks/subagent-hooks.d.ts +37 -0
  121. package/dist/hooks/subagent-hooks.d.ts.map +1 -0
  122. package/dist/hooks/subagent-hooks.js +79 -0
  123. package/dist/hooks/subagent-hooks.js.map +1 -0
  124. package/dist/hooks/subagent-tracker.d.ts +33 -0
  125. package/dist/hooks/subagent-tracker.d.ts.map +1 -0
  126. package/dist/hooks/subagent-tracker.js +73 -0
  127. package/dist/hooks/subagent-tracker.js.map +1 -0
  128. package/dist/hooks/tool-hooks.d.ts +132 -0
  129. package/dist/hooks/tool-hooks.d.ts.map +1 -0
  130. package/dist/hooks/tool-hooks.js +215 -0
  131. package/dist/hooks/tool-hooks.js.map +1 -0
  132. package/dist/index.d.ts +929 -0
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +236 -0
  135. package/dist/index.js.map +1 -0
  136. package/dist/logging/event-file-logger.d.ts +21 -0
  137. package/dist/logging/event-file-logger.d.ts.map +1 -0
  138. package/dist/logging/event-file-logger.js +167 -0
  139. package/dist/logging/event-file-logger.js.map +1 -0
  140. package/dist/logging/index.d.ts +3 -0
  141. package/dist/logging/index.d.ts.map +1 -0
  142. package/dist/logging/index.js +19 -0
  143. package/dist/logging/index.js.map +1 -0
  144. package/dist/logging/runtime-logger.d.ts +12 -0
  145. package/dist/logging/runtime-logger.d.ts.map +1 -0
  146. package/dist/logging/runtime-logger.js +45 -0
  147. package/dist/logging/runtime-logger.js.map +1 -0
  148. package/dist/runtime/hook-bridge-actions.d.ts +9 -0
  149. package/dist/runtime/hook-bridge-actions.d.ts.map +1 -0
  150. package/dist/runtime/hook-bridge-actions.js +248 -0
  151. package/dist/runtime/hook-bridge-actions.js.map +1 -0
  152. package/dist/runtime/hook-bridge-dispatch-engine.d.ts +42 -0
  153. package/dist/runtime/hook-bridge-dispatch-engine.d.ts.map +1 -0
  154. package/dist/runtime/hook-bridge-dispatch-engine.js +233 -0
  155. package/dist/runtime/hook-bridge-dispatch-engine.js.map +1 -0
  156. package/dist/runtime/hook-bridge-tool-guard.d.ts +16 -0
  157. package/dist/runtime/hook-bridge-tool-guard.d.ts.map +1 -0
  158. package/dist/runtime/hook-bridge-tool-guard.js +236 -0
  159. package/dist/runtime/hook-bridge-tool-guard.js.map +1 -0
  160. package/dist/runtime/hook-bridge-utils.d.ts +11 -0
  161. package/dist/runtime/hook-bridge-utils.d.ts.map +1 -0
  162. package/dist/runtime/hook-bridge-utils.js +116 -0
  163. package/dist/runtime/hook-bridge-utils.js.map +1 -0
  164. package/dist/runtime/hook-bridge.d.ts +33 -0
  165. package/dist/runtime/hook-bridge.d.ts.map +1 -0
  166. package/dist/runtime/hook-bridge.js +359 -0
  167. package/dist/runtime/hook-bridge.js.map +1 -0
  168. package/dist/runtime/internal-handlers.d.ts +20 -0
  169. package/dist/runtime/internal-handlers.d.ts.map +1 -0
  170. package/dist/runtime/internal-handlers.js +225 -0
  171. package/dist/runtime/internal-handlers.js.map +1 -0
  172. package/dist/runtime/register-gateway-hooks.d.ts +4 -0
  173. package/dist/runtime/register-gateway-hooks.d.ts.map +1 -0
  174. package/dist/runtime/register-gateway-hooks.js +27 -0
  175. package/dist/runtime/register-gateway-hooks.js.map +1 -0
  176. package/dist/runtime/register-session-hooks.d.ts +4 -0
  177. package/dist/runtime/register-session-hooks.d.ts.map +1 -0
  178. package/dist/runtime/register-session-hooks.js +88 -0
  179. package/dist/runtime/register-session-hooks.js.map +1 -0
  180. package/dist/runtime/register-subagent-hooks.d.ts +4 -0
  181. package/dist/runtime/register-subagent-hooks.d.ts.map +1 -0
  182. package/dist/runtime/register-subagent-hooks.js +143 -0
  183. package/dist/runtime/register-subagent-hooks.js.map +1 -0
  184. package/dist/runtime/register-tool-hooks.d.ts +4 -0
  185. package/dist/runtime/register-tool-hooks.d.ts.map +1 -0
  186. package/dist/runtime/register-tool-hooks.js +348 -0
  187. package/dist/runtime/register-tool-hooks.js.map +1 -0
  188. package/dist/runtime/runtime-events.d.ts +14 -0
  189. package/dist/runtime/runtime-events.d.ts.map +1 -0
  190. package/dist/runtime/runtime-events.js +166 -0
  191. package/dist/runtime/runtime-events.js.map +1 -0
  192. package/dist/runtime/typed-hooks.d.ts +9 -0
  193. package/dist/runtime/typed-hooks.d.ts.map +1 -0
  194. package/dist/runtime/typed-hooks.js +14 -0
  195. package/dist/runtime/typed-hooks.js.map +1 -0
  196. package/dist/runtime/types.d.ts +91 -0
  197. package/dist/runtime/types.d.ts.map +1 -0
  198. package/dist/runtime/types.js +3 -0
  199. package/dist/runtime/types.js.map +1 -0
  200. package/dist/runtime/utils.d.ts +47 -0
  201. package/dist/runtime/utils.d.ts.map +1 -0
  202. package/dist/runtime/utils.js +166 -0
  203. package/dist/runtime/utils.js.map +1 -0
  204. package/dist/tools/replay-tool-guard.d.ts +3 -0
  205. package/dist/tools/replay-tool-guard.d.ts.map +1 -0
  206. package/dist/tools/replay-tool-guard.js +96 -0
  207. package/dist/tools/replay-tool-guard.js.map +1 -0
  208. package/openclaw.plugin.json +847 -0
  209. package/package.json +63 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenClaw Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,473 @@
1
+ # OpenClaw Event Server Plugin
2
+
3
+ Most teams using OpenClaw hit the same wall: important agent activity is hard to operationalize because it lives in logs, scattered channels, or manual checks. This plugin solves that by turning agent behavior into clean, real-time events (WebSocket or webhook) that your automations can consume immediately, plus configurable Tool Guard controls to block risky tool calls and require human approval when needed. In practice, you install it, point events to your systems, and define guard rules.
4
+
5
+ Use cases:
6
+ - Send Slack alerts when tool calls fail or agents error.
7
+ - Feed `agent.status` and `agent.activity` into dashboards for live visibility.
8
+ - Trigger downstream jobs from session lifecycle events (`session.start`, `session.end`).
9
+ - Require human approval for risky tools (`web_search`, `web_fetch`, `browser`, `exec` with `sudo`).
10
+ - Write normalized events to long-term storage for audit, compliance, or analytics.
11
+ - Wake a sleeping parent agent when their sub agent stalls, finishes or times out
12
+ - Cost guardrails: monitor high-frequency tool loops and block when token/tool usage crosses per-session budgets.
13
+ - Knowledge capture: when a run succeeds after multiple failures, auto-log the successful tool sequence to docs/wiki.
14
+ - subsequent agent setup. Bot A finishes a writing a youtube script and goes idle, event server triggers an alert to Bot B to review script and make adjustments, OR if bot A fails or stalls, retries Bot A.
15
+
16
+
17
+ User senarios:
18
+ - Live content studio control room: a creator runs multiple agents for research, scripting, clipping, and posting. Event Server becomes the “producer dashboard” that shows who is working, who is stuck, and when a draft is ready. Tool Guard adds a human checkpoint before anything publishes or sends outreach.
19
+ - Founder daily ops autopilot: a small business owner uses agents for inbox triage, lead follow-up, and report generation. Event streams turn this into a visible operations timeline, so they can see bottlenecks and response speed. Tool Guard keeps high-risk actions (sending, spending, exporting) human-approved.
20
+ - Streamer has an openclaw agent monitoring his chat and uses it to manage interactivity in his live stream, Event server is feeding status updates and if the agent dies, stalls or goes inactive, calls a script to spawn a new agent with little to no downtime.
21
+ - Creative memory engine: every finished run emits structured “what worked” events into a reusable idea bank. Over time, creators build a searchable playbook of winning hooks, formats, and campaign patterns.
22
+
23
+
24
+ How does it actually work?
25
+ Openclaw Event Server Plugin is built around 3 things:
26
+ 1. Event server; This simply finds all the various events throughout openclaw and presents them in an easy to consume format.
27
+ 2. Hook Bridge; execute a script file or webhook when an event is captured
28
+ 3. Tool Guard; Openclaw ships with exec approval by default, but it does not have anything for other tools, this plugin fills that gap and allows users to create approval workflows or block possibly dangerous tool calls
29
+
30
+
31
+
32
+ It emits:
33
+ - Raw internal hook events (`message:*`, `command:*`, `agent:*`, `gateway:startup`)
34
+ - Raw plugin hook events (`before_tool_call`, `after_tool_call`, `tool_result_persist`, session/subagent/gateway typed hooks)
35
+ - Synthetic events (`agent.activity`, `agent.status`, `agent.sub_agent_spawn`)
36
+
37
+ ## Transport
38
+
39
+ - WebSocket broadcast server (default ports: `9011,9012,9013,9014,9015,9016`)
40
+ - HTTP webhooks with retry/queue support
41
+
42
+ ## Event Model
43
+
44
+ All emitted events use one canonical envelope so consumers can parse every event type consistently.
45
+
46
+ All emitted events use a canonical envelope:
47
+
48
+ - `eventId`
49
+ - `schemaVersion`
50
+ - `timestamp`
51
+ - `type`
52
+ - `eventCategory`
53
+ - `eventName`
54
+ - `source`
55
+ - `agentId`, `sessionId`, `sessionKey`, `runId`, `toolCallId` (when available)
56
+ - `correlationId`
57
+ - `result`/`error` (when relevant)
58
+ - `data`
59
+ - `metadata`
60
+
61
+ ### Supported event types
62
+
63
+ Synthetic event types are computed by this plugin and are not native upstream gateway events.
64
+
65
+ - Message: `message.received`, `message.transcribed`, `message.preprocessed`, `message.sent`
66
+ - Tool: `tool.called`, `tool.guard.matched`, `tool.guard.allowed`, `tool.guard.blocked`, `tool.completed`, `tool.error`, `tool.result_persist`
67
+ - Command: `command.new`, `command.reset`, `command.stop`
68
+ - Session: `session.start`, `session.end`
69
+ - Subagent: `subagent.spawning`, `subagent.spawned`, `subagent.ended`
70
+ - Subagent synthetic: `subagent.idle`
71
+ - Agent: `agent.bootstrap`, `agent.error`, `agent.session_start`, `agent.session_end`
72
+ - Gateway: `gateway.startup`, `gateway.start`, `gateway.stop`
73
+ - Synthetic: `agent.activity`, `agent.status`, `agent.sub_agent_spawn`
74
+ - Legacy aliases preserved for compatibility: `session.spawned`, `session.completed`, `session.error`
75
+
76
+ ## Install
77
+
78
+ Choose one install path:
79
+
80
+ 1. Install from npm (recommended for most users; no local build needed):
81
+
82
+ ```bash
83
+ openclaw plugins install openclaw-event-server-plugin
84
+ ```
85
+
86
+ 2. Install from local source (for contributors/dev):
87
+
88
+ ```bash
89
+ npm install
90
+ npm run build
91
+ openclaw plugins install -l /absolute/path/to/openclaw_event_server_plugin
92
+ ```
93
+
94
+ When installing from local source, `dist/` must be built before installation.
95
+
96
+ ## Compatibility
97
+
98
+ Compatibility is pinned to a known OpenClaw hook surface and enforced by contract tests.
99
+
100
+ - Pinned hook-surface fixture: OpenClaw commit `7b5e64ef2e369258e2a4a613b7a62db3c21e5160`.
101
+ - Compatibility is enforced by fixture-driven contract tests (`tests/contract/openclaw-hook-surface.test.ts`).
102
+ - Additional versions can be documented by adding new hook-surface fixtures when validated.
103
+
104
+ ## Configure
105
+
106
+ Use this as the primary runtime config for transport, retries, logging, security, filtering, redaction, status timing, and automation behavior.
107
+
108
+ In `~/.openclaw/openclaw.json`:
109
+
110
+ ```json
111
+ {
112
+ "plugins": {
113
+ "entries": {
114
+ "event-server-plugin": {
115
+ "enabled": true,
116
+ "config": {
117
+ "webhooks": [
118
+ {
119
+ "url": "https://example.com/events",
120
+ "method": "POST"
121
+ }
122
+ ],
123
+ "queue": {
124
+ "maxSize": 1000,
125
+ "flushIntervalMs": 5000,
126
+ "persistToDisk": false
127
+ },
128
+ "status": {
129
+ "workingWindowMs": 30000,
130
+ "sleepingWindowMs": 600000,
131
+ "tickIntervalMs": 5000,
132
+ "subagentIdleWindowMs": 300000
133
+ },
134
+ "redaction": {
135
+ "enabled": false,
136
+ "replacement": "[REDACTED]",
137
+ "fields": ["content", "params", "token", "authorization"]
138
+ },
139
+ "eventLog": {
140
+ "enabled": true,
141
+ "path": ".event-server/events.ndjson",
142
+ "maxFileSizeMb": 30,
143
+ "format": "full-json",
144
+ "minLevel": "debug",
145
+ "includeRuntimeLogs": true
146
+ },
147
+ "security": {
148
+ "ws": {
149
+ "bindAddress": "127.0.0.1",
150
+ "requireAuth": false,
151
+ "authToken": "",
152
+ "allowedOrigins": [],
153
+ "allowedIps": []
154
+ },
155
+ "hmac": {
156
+ "enabled": false,
157
+ "secretFilePath": ".event-plugin-hmac.secret",
158
+ "algorithm": "sha256"
159
+ }
160
+ },
161
+ "retry": {
162
+ "maxAttempts": 3,
163
+ "initialDelayMs": 1000,
164
+ "maxDelayMs": 30000,
165
+ "backoffMultiplier": 2
166
+ },
167
+ "filters": {
168
+ "includeTypes": [],
169
+ "excludeTypes": []
170
+ },
171
+ "hookBridge": {
172
+ "enabled": false,
173
+ "dryRun": false,
174
+ "allowedActionDirs": ["/absolute/path/to/hooks"],
175
+ "localScriptDefaults": {
176
+ "timeoutMs": 10000,
177
+ "maxPayloadBytes": 65536
178
+ },
179
+ "actions": {
180
+ "sudo-alert": {
181
+ "type": "webhook",
182
+ "url": "https://example.com/hook/sudo-alert",
183
+ "method": "POST"
184
+ },
185
+ "wake-parent": {
186
+ "type": "local_script",
187
+ "path": "/absolute/path/to/hooks/wake-parent.sh",
188
+ "args": []
189
+ }
190
+ },
191
+ "rules": [
192
+ {
193
+ "id": "notify-sudo",
194
+ "when": {
195
+ "eventType": "tool.called",
196
+ "toolName": "exec",
197
+ "contains": {
198
+ "data.params.command": "sudo"
199
+ }
200
+ },
201
+ "action": "sudo-alert",
202
+ "cooldownMs": 60000
203
+ }
204
+ ],
205
+ "toolGuard": {
206
+ "enabled": false,
207
+ "dryRun": false,
208
+ "timeoutMs": 15000,
209
+ "onError": "allow",
210
+ "rules": [
211
+ {
212
+ "id": "approve-exec",
213
+ "when": {
214
+ "toolName": "exec"
215
+ },
216
+ "action": "wake-parent"
217
+ }
218
+ ]
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ ## Environment Variables
229
+
230
+ Use environment variables for quick overrides in containers, CI, and one-off debugging.
231
+
232
+ - `EVENT_PLUGIN_WEBHOOKS` comma-separated webhook URLs
233
+ - `EVENT_PLUGIN_AUTH_TOKEN` bearer token applied to env-defined webhooks
234
+ - `EVENT_PLUGIN_DEBUG` enable debug logging
235
+ - `EVENT_PLUGIN_INCLUDE_TYPES` comma-separated include filters
236
+ - `EVENT_PLUGIN_EXCLUDE_TYPES` comma-separated exclude filters
237
+ - `EVENT_PLUGIN_ENABLED` enable/disable plugin
238
+ - `EVENT_PLUGIN_WS_PORTS` comma-separated WS fallback order
239
+ - `EVENT_PLUGIN_DISABLE_WS` disable WS server
240
+ - `EVENT_PLUGIN_DISABLE_STATUS_TICKER` disable periodic synthetic status ticks (tests/CI)
241
+ - `EVENT_PLUGIN_TOOL_GUARD_TRACE` set `1`/`true` to emit verbose Tool Guard evaluation/action traces
242
+ - `EVENT_PLUGIN_STATUS_WORKING_WINDOW_MS` override working activity window
243
+ - `EVENT_PLUGIN_STATUS_SLEEPING_WINDOW_MS` override sleeping window
244
+ - `EVENT_PLUGIN_STATUS_TICK_INTERVAL_MS` override status ticker interval
245
+ - `EVENT_PLUGIN_STATUS_SUBAGENT_IDLE_WINDOW_MS` override `subagent.idle` threshold
246
+ - `EVENT_PLUGIN_REDACTION_ENABLED` enable payload redaction (default `false`)
247
+ - `EVENT_PLUGIN_REDACTION_REPLACEMENT` replacement text for redacted values
248
+ - `EVENT_PLUGIN_REDACTION_FIELDS` comma-separated key names to redact recursively
249
+ - `EVENT_PLUGIN_EVENT_LOG_ENABLED` enable/disable NDJSON file logging
250
+ - `EVENT_PLUGIN_EVENT_LOG_PATH` override log file path
251
+ - `EVENT_PLUGIN_EVENT_LOG_MAX_FILE_MB` max NDJSON log size in MB before truncation rollover (default `30`)
252
+ - `EVENT_PLUGIN_EVENT_LOG_FORMAT` `full-json` or `summary`
253
+ - `EVENT_PLUGIN_EVENT_LOG_MIN_LEVEL` `debug|info|warn|error` for runtime log records
254
+ - `EVENT_PLUGIN_EVENT_LOG_RUNTIME` include runtime log entries in file output
255
+ - `EVENT_PLUGIN_WS_BIND_ADDRESS` WS bind address (default `127.0.0.1`)
256
+ - `EVENT_PLUGIN_WS_REQUIRE_AUTH` require WS token auth
257
+ - `EVENT_PLUGIN_WS_AUTH_TOKEN` shared WS token
258
+ - `EVENT_PLUGIN_WS_ALLOWED_ORIGINS` comma-separated allowlist for WS `Origin` header
259
+ - `EVENT_PLUGIN_WS_ALLOWED_IPS` comma-separated WS client IP allowlist
260
+ - `EVENT_PLUGIN_HMAC_ENABLED` enable event HMAC signing
261
+ - `EVENT_PLUGIN_HMAC_SECRET` inline shared HMAC secret
262
+ - `EVENT_PLUGIN_HMAC_SECRET_FILE` file path for shared HMAC secret
263
+ - `EVENT_PLUGIN_HMAC_ALGORITHM` `sha256` or `sha512`
264
+
265
+ ## Agent Status Semantics
266
+
267
+ `agent.status` is derived across all known sessions for each agent.
268
+
269
+ Example: if a parent agent starts a session and then appears quiet, status can still be `working` while a subagent is actively processing.
270
+
271
+ - `working`: activity in last `status.workingWindowMs` (default 30s)
272
+ - `idle`: no activity for > `status.workingWindowMs` and <= `status.sleepingWindowMs`
273
+ - `sleeping`: no activity for > `status.sleepingWindowMs` (default 10m)
274
+ - `offline`: marked offline (for example gateway stop/offline agent error classification)
275
+ - `error`: agent-level error latch
276
+
277
+ `tool.error` does not automatically force `agent.status=error`.
278
+
279
+ ## Redaction
280
+
281
+ Payload redaction is opt-in and disabled by default. When enabled, the plugin redacts configured key names recursively across `data`, `metadata`, and nested payload objects before broadcasting to WebSocket and HTTP webhooks.
282
+
283
+ ## Subagent Tracking
284
+
285
+ Subagent lifecycle and workload can be tracked independently:
286
+
287
+ - `subagent.spawned` carries parent + child identity fields.
288
+ - Tool events include `subagentKey`, `parentAgentId`, `parentSessionKey` when tool calls are associated with a child session.
289
+ - `subagent.idle` is emitted when a spawned child session has no observed activity for `status.subagentIdleWindowMs`.
290
+ - `agent.activity` for `subagent.idle` is emitted only when parent/child `agentId` is known; the plugin does not emit synthetic `"unknown"` agent identities.
291
+
292
+ This enables Mission Control tree views (parent agent with per-subagent status/tool lanes) while preserving top-level `agent.status` aggregation.
293
+
294
+ ## Event Logging
295
+
296
+ `eventLog` writes NDJSON to disk from inside the plugin runtime.
297
+
298
+ - `format=full-json` (default): complete canonical event envelope in each line.
299
+ - `format=summary`: reduced envelope fields for lower volume.
300
+ - runtime records can be included and filtered by `minLevel`.
301
+ - `maxFileSizeMb=30` (default): logger truncates and continues once the file reaches the size cap.
302
+
303
+ Default path is `.event-server/events.ndjson`.
304
+
305
+ Relative `eventLog.path` values are resolved at runtime in this order:
306
+ - `OPENCLAW_STATE_DIR` (if set)
307
+ - directory containing `OPENCLAW_CONFIG_PATH` (if set)
308
+ - `~/.openclaw/`
309
+
310
+ This keeps default config portable while avoiding service working-directory issues.
311
+
312
+ ## Security
313
+
314
+ Recommended defaults for community deployments:
315
+
316
+ - WS binds to localhost (`127.0.0.1`) by default.
317
+ - WS auth can be enabled with a shared token.
318
+ - Optional origin/IP allowlists for WS clients.
319
+ - Optional HMAC event signing (`hmac.enabled=false` by default).
320
+ - Native TLS/WSS termination is not provided by this plugin; run it behind a reverse proxy
321
+ (for example Nginx/Caddy/Traefik) for production HTTPS/WSS.
322
+
323
+ For local setups, keep the shared HMAC secret in `.event-plugin-hmac.secret`. Set `security.hmac.enabled=true` (or `EVENT_PLUGIN_HMAC_ENABLED=true`) to enable event signing.
324
+
325
+ ## Contributing
326
+
327
+ If you want to contribute code, docs, or examples:
328
+
329
+ - Read [CONTRIBUTING.md](CONTRIBUTING.md) for workflow and PR expectations.
330
+ - Read [SECURITY.md](SECURITY.md) for responsible vulnerability reporting.
331
+
332
+ ## Hook Bridge Automation
333
+
334
+ `hookBridge` enables event-driven automations directly from canonical plugin events. It can execute a local script or call a webhook when a matching event occurs.
335
+
336
+ - Match rules by `eventType`, identity fields, nested field checks, idle thresholds, and parent status.
337
+ - Dispatch actions as:
338
+ - `webhook` (`POST|PUT|PATCH` with JSON payload)
339
+ - `local_script` (fixed script path + args, event payload over stdin)
340
+ - Suppress repeated triggers with per-rule `cooldownMs`.
341
+
342
+ Local script actions are restricted to `allowedActionDirs` and run with `shell=false`.
343
+ Default runtime limits for local scripts are configured in `localScriptDefaults`.
344
+
345
+ ### Tool Guard (Optional human in the middle)
346
+
347
+ `hookBridge.toolGuard` adds synchronous `before_tool_call` policy checks using the same `actions` registry.
348
+ By default it is disabled and does not affect tool execution.
349
+
350
+ - `enabled=false` (default): no blocking behavior.
351
+ - `rules`: ordered checks for tool-call context:
352
+ - `toolName`, `contains`, `equals`, `matchesRegex`, `notMatchesRegex`
353
+ - `requiredPaths`, `typeChecks`, `inList`, `notInList`
354
+ - `domainAllowlist` / `domainBlocklist` (optional `domainPath`, default `data.params.url`)
355
+ - agent/session fields
356
+ - `onError`:
357
+ - `allow` (default) fail-open on script/webhook error or timeout
358
+ - `block` fail-closed
359
+ - `dryRun=true`: evaluates/logs decisions but never blocks.
360
+ - `priority`: higher-priority rules evaluate first (ties keep config order).
361
+ - `stopOnMatch`: stop evaluating additional rules when a rule matches but returns no decision
362
+ (rules that return a decision already short-circuit).
363
+ - `retryBackoffMs`: force backoff when a blocked call is retried repeatedly.
364
+ - `approvalCacheTtlMs`: cache allow decisions for repeat calls.
365
+ - `scopeKeyBy`: `tool` or `tool_and_params` for retry/cache keys.
366
+ - `redaction` (default off): optional redaction for `tool.guard.*` event `data.params`.
367
+ - Each rule can either:
368
+ - call an `action` (webhook/local script), or
369
+ - use an inline static `decision` for simple validation/guidance.
370
+
371
+ Guard action response contract (webhook response body or local script stdout):
372
+
373
+ ```json
374
+ { "block": true, "blockReason": "Manual approval required" }
375
+ ```
376
+
377
+ or
378
+
379
+ ```json
380
+ { "params": { "mode": "safe" } }
381
+ ```
382
+
383
+ Invalid decision payloads are ignored. A valid decision must include either:
384
+ - `block` as a boolean, or
385
+ - `params` as an object.
386
+
387
+ For true blocking behavior (recommended for approvals):
388
+ - Set `hookBridge.toolGuard.onError` to `block` (fail-closed).
389
+ - Use a decision script that defaults to block on timeout/error.
390
+ - Require explicit human approvers (`OPENCLAW_APPROVAL_ALLOWED_USER_IDS` or profile `allowedUserIds`) so bot reactions/replies cannot auto-approve.
391
+
392
+ Example invalid payload (ignored):
393
+
394
+ ```json
395
+ { "blockReason": "manual approval required" }
396
+ ```
397
+
398
+ Templated guidance is supported in `blockReasonTemplate` (or `blockReason`):
399
+ - `{{toolName}}`, `{{eventType}}`, `{{agentId}}`, `{{sessionId}}`, `{{sessionKey}}`, `{{runId}}`, `{{toolCallId}}`
400
+ - `{{path:data.params.url}}` for dotted-path lookups
401
+
402
+ Example malformed-call filter without scripting:
403
+
404
+ ```json
405
+ {
406
+ "id": "web-fetch-url-must-be-https",
407
+ "priority": 100,
408
+ "when": {
409
+ "toolName": "web_fetch",
410
+ "notMatchesRegex": {
411
+ "data.params.url": "^https://"
412
+ }
413
+ },
414
+ "decision": {
415
+ "block": true,
416
+ "blockReasonTemplate": "Malformed {{toolName}} URL: {{path:data.params.url}}. Use: {{toolName}} \"https://...\""
417
+ }
418
+ }
419
+ ```
420
+
421
+ ### Tool Guard Replay
422
+
423
+ You can replay captured tool calls against `toolGuard` policies before rollout:
424
+
425
+ From a local source checkout:
426
+
427
+ ```bash
428
+ npm run build
429
+ jq '.plugins.entries["event-server-plugin"].config' ~/.openclaw/openclaw.json > /tmp/event-plugin-config.json
430
+ npm run toolguard:replay -- --config /tmp/event-plugin-config.json --input ./calls.ndjson
431
+ ```
432
+
433
+ Input format supports:
434
+ - JSON array of tool call objects
435
+ - NDJSON (one tool call object per line)
436
+
437
+ Each call object fields:
438
+ - `toolName` (required)
439
+ - `params` (object)
440
+ - optional `agentId`, `sessionId`, `sessionKey`, `runId`, `toolCallId`
441
+
442
+ ### Tool Guard Bundles
443
+
444
+ Starter bundles are included under `examples/tool-guard-bundles/`:
445
+ - `network-egress.json`
446
+ - `shell-guard.json`
447
+ - `sudo-slack-approval-allow.json` (sends Slack approval requests for `sudo` calls, then allows execution)
448
+ - `web-browse-human-approval.json` (requires explicit human approval for `web_search`, `web_fetch`, and `browser`; posts a Slack approval request and waits for approve/reject reply or reaction)
449
+ - annotated versions for bot/user guidance are also included as `*.annotated.jsonc`
450
+
451
+ Example local scripts for `local_script` actions are under `examples/tool-guard-bundles/scripts/`:
452
+ - `sudo-slack-approval-and-allow.sh`
453
+ - `web-browse-slack-human-approval.sh` (interactive Slack approvals via replies/reactions; requires longer `toolGuard.timeoutMs`)
454
+ - script is fail-closed by default (`OPENCLAW_APPROVAL_REQUIRE_ALLOWED_USERS=true`)
455
+ - rejects approvals from bot users
456
+ - supports `web_search`, `web_fetch`, `browser` (or any tool when used in a matching rule)
457
+
458
+ Optional approval channel profiles example:
459
+ - `toolguard-approval-profiles.example.json` (copy to `~/.openclaw/toolguard-approval-profiles.json` and edit)
460
+ - include `allowedUserIds` to restrict who can approve/reject
461
+
462
+ ## Development
463
+
464
+ ```bash
465
+ npm run lint
466
+ npm run build
467
+ npm test -- --runInBand
468
+ ```
469
+
470
+ ## Notes
471
+
472
+ - The plugin is intentionally "dumb" for event forwarding: payload transformation is minimal.
473
+ - Downstream consumers should own filtering/aggregation beyond canonical normalization.
@@ -0,0 +1,203 @@
1
+ {
2
+ "enabled": true,
3
+ "webhooks": [
4
+ {
5
+ "url": "https://your-webhook-endpoint.com/events",
6
+ "method": "POST",
7
+ "headers": {
8
+ "Content-Type": "application/json"
9
+ },
10
+ "authToken": "your-auth-token-here",
11
+ "includeFullPayload": true
12
+ }
13
+ ],
14
+ "filters": {
15
+ "includeTypes": [],
16
+ "excludeTypes": [],
17
+ "channelId": "",
18
+ "toolName": "",
19
+ "sessionId": ""
20
+ },
21
+ "retry": {
22
+ "maxAttempts": 3,
23
+ "initialDelayMs": 1000,
24
+ "maxDelayMs": 30000,
25
+ "backoffMultiplier": 2
26
+ },
27
+ "queue": {
28
+ "maxSize": 1000,
29
+ "flushIntervalMs": 5000,
30
+ "persistToDisk": false,
31
+ "persistPath": ""
32
+ },
33
+ "logging": {
34
+ "debug": false,
35
+ "logSuccess": false,
36
+ "logErrors": true,
37
+ "logQueue": false
38
+ },
39
+ "status": {
40
+ "workingWindowMs": 30000,
41
+ "sleepingWindowMs": 600000,
42
+ "tickIntervalMs": 5000,
43
+ "subagentIdleWindowMs": 300000
44
+ },
45
+ "redaction": {
46
+ "enabled": false,
47
+ "replacement": "[REDACTED]",
48
+ "fields": [
49
+ "content",
50
+ "newContent",
51
+ "originalContent",
52
+ "transcript",
53
+ "normalizedText",
54
+ "params",
55
+ "arguments",
56
+ "token",
57
+ "apiKey",
58
+ "api_key",
59
+ "authorization",
60
+ "authToken",
61
+ "password",
62
+ "secret"
63
+ ]
64
+ },
65
+ "eventLog": {
66
+ "enabled": true,
67
+ "path": ".event-server/events.ndjson",
68
+ "maxFileSizeMb": 30,
69
+ "format": "full-json",
70
+ "minLevel": "debug",
71
+ "includeRuntimeLogs": true
72
+ },
73
+ "security": {
74
+ "ws": {
75
+ "bindAddress": "127.0.0.1",
76
+ "requireAuth": false,
77
+ "authToken": "",
78
+ "allowedOrigins": [],
79
+ "allowedIps": []
80
+ },
81
+ "hmac": {
82
+ "enabled": false,
83
+ "secret": "",
84
+ "secretFilePath": ".event-plugin-hmac.secret",
85
+ "algorithm": "sha256"
86
+ }
87
+ },
88
+ "correlationIdHeader": "X-Correlation-ID",
89
+ "webhookTimeoutMs": 10000,
90
+ "hookBridge": {
91
+ "enabled": false,
92
+ "dryRun": false,
93
+ "allowedActionDirs": ["/absolute/path/to/hooks"],
94
+ "localScriptDefaults": {
95
+ "timeoutMs": 10000,
96
+ "maxPayloadBytes": 65536
97
+ },
98
+ "actions": {
99
+ "sudo-alert-webhook": {
100
+ "type": "webhook",
101
+ "url": "https://example.com/hook/sudo-alert",
102
+ "method": "POST",
103
+ "headers": {
104
+ "X-Source": "openclaw-event-server-plugin"
105
+ },
106
+ "timeoutMs": 10000
107
+ },
108
+ "wake-parent-local": {
109
+ "type": "local_script",
110
+ "path": "/absolute/path/to/hooks/wake-parent.sh",
111
+ "args": [],
112
+ "timeoutMs": 10000,
113
+ "maxPayloadBytes": 65536
114
+ }
115
+ },
116
+ "rules": [
117
+ {
118
+ "id": "notify-sudo-usage",
119
+ "enabled": true,
120
+ "when": {
121
+ "eventType": "tool.called",
122
+ "toolName": "exec",
123
+ "contains": {
124
+ "data.params.command": "sudo"
125
+ }
126
+ },
127
+ "action": "sudo-alert-webhook",
128
+ "cooldownMs": 60000,
129
+ "coalesce": {
130
+ "enabled": false
131
+ }
132
+ },
133
+ {
134
+ "id": "wake-parent-on-subagent-idle",
135
+ "enabled": true,
136
+ "when": {
137
+ "eventType": "subagent.idle",
138
+ "idleForMsGte": 300000,
139
+ "parentStatus": "sleeping"
140
+ },
141
+ "action": "wake-parent-local",
142
+ "cooldownMs": 120000
143
+ }
144
+ ],
145
+ "toolGuard": {
146
+ "enabled": false,
147
+ "dryRun": false,
148
+ "timeoutMs": 15000,
149
+ "onError": "allow",
150
+ "scopeKeyBy": "tool_and_params",
151
+ "retryBackoffMs": 10000,
152
+ "retryBackoffReason": "Retry blocked. Back off briefly before retrying this tool call.",
153
+ "approvalCacheTtlMs": 60000,
154
+ "stopOnMatchDefault": false,
155
+ "redaction": {
156
+ "enabled": false,
157
+ "replacement": "[REDACTED]",
158
+ "fields": ["token", "authorization", "apiKey", "password", "secret"]
159
+ },
160
+ "rules": [
161
+ {
162
+ "id": "web-browse-url-must-be-https",
163
+ "priority": 100,
164
+ "enabled": true,
165
+ "when": {
166
+ "toolName": "web_browse",
167
+ "notMatchesRegex": {
168
+ "data.params.url": "^https://"
169
+ }
170
+ },
171
+ "decision": {
172
+ "block": true,
173
+ "blockReason": "Malformed web_browse URL. Use: web_browse \"https://...\""
174
+ },
175
+ "cooldownMs": 0
176
+ },
177
+ {
178
+ "id": "human-approve-exec",
179
+ "priority": 10,
180
+ "enabled": false,
181
+ "when": {
182
+ "toolName": "exec"
183
+ },
184
+ "action": "wake-parent-local",
185
+ "cooldownMs": 0
186
+ }
187
+ ]
188
+ },
189
+ "runtime": {
190
+ "maxPendingEvents": 1000,
191
+ "concurrency": 8,
192
+ "dropPolicy": "drop_oldest"
193
+ },
194
+ "telemetry": {
195
+ "highWatermarks": [70, 90, 100],
196
+ "slowActionMs": 2000,
197
+ "failureRateWindowMs": 60000,
198
+ "failureRateThresholdPct": 20,
199
+ "failureRateMinSamples": 10,
200
+ "saturationWindowMs": 10000
201
+ }
202
+ }
203
+ }