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 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;
@@ -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\" (the primary agent session).",
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.2.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
- "description": "OpenClaw plugin: AgentMail listener \u2014 injects system events when emails arrive",
5
+ "description": "OpenClaw plugin: AgentMail listener injects system events when emails arrive",
6
6
  "files": [
7
7
  "dist",
8
8
  "openclaw.plugin.json",