openclaw-agentmail-listener 0.2.1 → 0.3.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/README.md +16 -0
- package/dist/index.js +61 -1
- package/openclaw.plugin.json +13 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ Add to your OpenClaw config under `plugins.entries.agentmail-listener.config`:
|
|
|
78
78
|
| `inboxId` | string | ✅ | — | Inbox to subscribe (e.g. `nickbot@agentmail.to`) |
|
|
79
79
|
| `eventTypes` | string[] | — | `["message.received"]` | Event types to subscribe to |
|
|
80
80
|
| `sessionKey` | string | — | `"agent:main:main"` | Agent session key for routing system events |
|
|
81
|
+
| `wake` | string | — | `"tools-invoke"` | Wake method: `"tools-invoke"` (default), `"hooks"`, or `"off"` |
|
|
81
82
|
|
|
82
83
|
## System event format
|
|
83
84
|
|
|
@@ -92,12 +93,27 @@ Preview: This is the first 200 chars of the email body...
|
|
|
92
93
|
|
|
93
94
|
The event is keyed with `contextKey: agentmail:<messageId>` to deduplicate repeated events for the same message.
|
|
94
95
|
|
|
96
|
+
## Instant wake
|
|
97
|
+
|
|
98
|
+
By default, system events are only processed when the agent's heartbeat fires (which can be up to 1 hour). This plugin triggers an **immediate wake** after enqueuing each email event by calling the gateway's cron wake endpoint (`POST /tools/invoke`).
|
|
99
|
+
|
|
100
|
+
### Wake methods
|
|
101
|
+
|
|
102
|
+
| Method | Auth | When to use |
|
|
103
|
+
|--------|------|-------------|
|
|
104
|
+
| `tools-invoke` (default) | `gateway.auth.token` | Works out of the box, no extra config |
|
|
105
|
+
| `hooks` | `hooks.token` | If you already have hooks enabled |
|
|
106
|
+
| `off` | — | Disable wake; rely on heartbeat polling |
|
|
107
|
+
|
|
108
|
+
The plugin reads port and token from your OpenClaw config automatically.
|
|
109
|
+
|
|
95
110
|
## Architecture notes
|
|
96
111
|
|
|
97
112
|
- The plugin uses the `agentmail` npm package's WebSocket client
|
|
98
113
|
- Reconnection uses exponential backoff (1s → 2s → 4s → ... → 60s max) with ±10% jitter
|
|
99
114
|
- Re-subscription happens automatically on each reconnect (WebSocket `open` event)
|
|
100
115
|
- Uses `api.runtime.system.enqueueSystemEvent()` to route events to the agent session
|
|
116
|
+
- After enqueuing, triggers an immediate agent wake via the gateway's `/tools/invoke` endpoint (cron wake action) so the agent processes the email right away instead of waiting for the next heartbeat poll
|
|
101
117
|
|
|
102
118
|
## Dependencies
|
|
103
119
|
|
package/dist/index.js
CHANGED
|
@@ -13027,11 +13027,13 @@ function register(api) {
|
|
|
13027
13027
|
api.logger.warn("agentmail-listener: no inboxId configured \u2014 service not starting");
|
|
13028
13028
|
return;
|
|
13029
13029
|
}
|
|
13030
|
+
const wakeMode = cfg.wake === "hooks" || cfg.wake === "off" ? cfg.wake : "tools-invoke";
|
|
13030
13031
|
const pluginCfg = {
|
|
13031
13032
|
apiKey: cfg.apiKey,
|
|
13032
13033
|
inboxId: cfg.inboxId,
|
|
13033
13034
|
eventTypes: cfg.eventTypes ?? ["message.received"],
|
|
13034
|
-
sessionKey: cfg.sessionKey ?? "agent:main:main"
|
|
13035
|
+
sessionKey: cfg.sessionKey ?? "agent:main:main",
|
|
13036
|
+
wake: wakeMode
|
|
13035
13037
|
};
|
|
13036
13038
|
startListener(api, pluginCfg);
|
|
13037
13039
|
},
|
|
@@ -13152,6 +13154,10 @@ function handleEvent(api, cfg, event) {
|
|
|
13152
13154
|
sessionKey: cfg.sessionKey ?? "agent:main:main",
|
|
13153
13155
|
contextKey: `agentmail:${messageId}`
|
|
13154
13156
|
});
|
|
13157
|
+
const wakeText = "You have a new agentmail email. Check the pending system event for details.";
|
|
13158
|
+
wakeAgent(api, cfg.wake ?? "tools-invoke", wakeText).catch((err) => {
|
|
13159
|
+
api.logger.warn(`agentmail-listener: wake failed (event still queued): ${String(err)}`);
|
|
13160
|
+
});
|
|
13155
13161
|
} catch (err) {
|
|
13156
13162
|
api.logger.error(`agentmail-listener: failed to enqueue system event: ${String(err)}`);
|
|
13157
13163
|
}
|
|
@@ -13164,6 +13170,60 @@ function handleEvent(api, cfg, event) {
|
|
|
13164
13170
|
);
|
|
13165
13171
|
}
|
|
13166
13172
|
}
|
|
13173
|
+
async function wakeAgent(api, mode, wakeText) {
|
|
13174
|
+
if (mode === "off") return;
|
|
13175
|
+
const cfg = api.runtime.config.loadConfig();
|
|
13176
|
+
const port = cfg.gateway?.port ?? 18789;
|
|
13177
|
+
if (mode === "hooks") {
|
|
13178
|
+
const hooksToken = cfg.hooks?.token;
|
|
13179
|
+
if (!hooksToken) {
|
|
13180
|
+
api.logger.warn("agentmail-listener: wake=hooks but no hooks.token configured");
|
|
13181
|
+
return;
|
|
13182
|
+
}
|
|
13183
|
+
const hooksPath = (cfg.hooks?.path ?? "/hooks").replace(/\/+$/, "");
|
|
13184
|
+
const url2 = `http://127.0.0.1:${port}${hooksPath}/wake`;
|
|
13185
|
+
const res2 = await fetch(url2, {
|
|
13186
|
+
method: "POST",
|
|
13187
|
+
headers: {
|
|
13188
|
+
Authorization: `Bearer ${hooksToken}`,
|
|
13189
|
+
"Content-Type": "application/json"
|
|
13190
|
+
},
|
|
13191
|
+
body: JSON.stringify({ text: wakeText, mode: "now" })
|
|
13192
|
+
});
|
|
13193
|
+
if (!res2.ok) {
|
|
13194
|
+
const body = await res2.text().catch(() => "");
|
|
13195
|
+
throw new Error(`hooks/wake returned ${res2.status}: ${body}`);
|
|
13196
|
+
}
|
|
13197
|
+
api.logger.info("agentmail-listener: agent wake triggered via hooks");
|
|
13198
|
+
return;
|
|
13199
|
+
}
|
|
13200
|
+
const gatewayToken = cfg.gateway?.auth?.token;
|
|
13201
|
+
if (!gatewayToken) {
|
|
13202
|
+
api.logger.warn("agentmail-listener: wake=tools-invoke but no gateway.auth.token configured");
|
|
13203
|
+
return;
|
|
13204
|
+
}
|
|
13205
|
+
const url = `http://127.0.0.1:${port}/tools/invoke`;
|
|
13206
|
+
const res = await fetch(url, {
|
|
13207
|
+
method: "POST",
|
|
13208
|
+
headers: {
|
|
13209
|
+
Authorization: `Bearer ${gatewayToken}`,
|
|
13210
|
+
"Content-Type": "application/json"
|
|
13211
|
+
},
|
|
13212
|
+
body: JSON.stringify({
|
|
13213
|
+
tool: "cron",
|
|
13214
|
+
args: {
|
|
13215
|
+
action: "wake",
|
|
13216
|
+
text: wakeText,
|
|
13217
|
+
mode: "now"
|
|
13218
|
+
}
|
|
13219
|
+
})
|
|
13220
|
+
});
|
|
13221
|
+
if (!res.ok) {
|
|
13222
|
+
const body = await res.text().catch(() => "");
|
|
13223
|
+
throw new Error(`tools/invoke cron wake returned ${res.status}: ${body}`);
|
|
13224
|
+
}
|
|
13225
|
+
api.logger.info("agentmail-listener: agent wake triggered via tools/invoke");
|
|
13226
|
+
}
|
|
13167
13227
|
var MIN_DELAY_MS = 1e3;
|
|
13168
13228
|
var MAX_DELAY_MS = 6e4;
|
|
13169
13229
|
var BACKOFF_FACTOR = 2;
|
package/openclaw.plugin.json
CHANGED
|
@@ -37,8 +37,14 @@
|
|
|
37
37
|
},
|
|
38
38
|
"sessionKey": {
|
|
39
39
|
"type": "string",
|
|
40
|
-
"description": "Agent session key to deliver system events to. Defaults to \"main\"
|
|
41
|
-
"default": "main"
|
|
40
|
+
"description": "Agent session key to deliver system events to. Defaults to \"agent:main:main\".",
|
|
41
|
+
"default": "agent:main:main"
|
|
42
|
+
},
|
|
43
|
+
"wake": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"enum": ["tools-invoke", "hooks", "off"],
|
|
46
|
+
"description": "How to wake the agent on new email. \"tools-invoke\" (default) uses gateway auth, \"hooks\" uses hooks.token, \"off\" disables wake.",
|
|
47
|
+
"default": "tools-invoke"
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
},
|
|
@@ -58,7 +64,11 @@
|
|
|
58
64
|
},
|
|
59
65
|
"sessionKey": {
|
|
60
66
|
"label": "Agent Session Key",
|
|
61
|
-
"placeholder": "main"
|
|
67
|
+
"placeholder": "agent:main:main"
|
|
68
|
+
},
|
|
69
|
+
"wake": {
|
|
70
|
+
"label": "Wake Method",
|
|
71
|
+
"placeholder": "tools-invoke"
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-agentmail-listener",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OpenClaw plugin: AgentMail listener
|
|
5
|
+
"description": "OpenClaw plugin: AgentMail listener — injects system events when emails arrive",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"openclaw.plugin.json",
|