@hzhipeng/heartbeat-notify 1.0.1 → 2026.7.1

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
@@ -6,18 +6,4 @@ The tool posts structured heartbeat notification decisions to:
6
6
 
7
7
  `/openclaw-gateway/be/internal/heartbeat/notify`
8
8
 
9
- Use `notify=false` for no-op heartbeat checks. Use `notify=true` only when `HEARTBEAT.md` has selected user-visible text.
10
-
11
- Minimal call shape:
12
-
13
- ```json
14
- { "notify": true, "text": "Reminder text" }
15
- ```
16
-
17
- For group notifications, also pass `group_id`:
18
-
19
- ```json
20
- { "notify": true, "text": "Group reminder text", "group_id": "group-id" }
21
- ```
22
-
23
- Do not ask the model to provide `conversation_id`, `sender_id`, `lobster_id`, or `bot_id`. Gateway resolves the delivery route from the current agent/session context.
9
+ Use `notify=false` for no-op heartbeat checks. Use `notify=true` only when `HEARTBEAT.md` has selected a concrete target and user-visible text.
package/index.js CHANGED
@@ -72,42 +72,8 @@ function targetString(params, snake, camel) {
72
72
  return trimString(params[snake]) || trimString(params[camel]);
73
73
  }
74
74
 
75
- function contextString(source, ...keys) {
76
- if (!source || typeof source !== "object") {
77
- return "";
78
- }
79
- for (const key of keys) {
80
- const value = trimString(source[key]);
81
- if (value) {
82
- return value;
83
- }
84
- }
85
- return "";
86
- }
87
-
88
- function firstString(...values) {
89
- for (const value of values) {
90
- const text = trimString(value);
91
- if (text) {
92
- return text;
93
- }
94
- }
95
- return "";
96
- }
97
-
98
- function buildRequest(params, ctx) {
75
+ function buildRequest(params) {
99
76
  const notify = boolValue(params.notify);
100
- const safeCtx = ctx && typeof ctx === "object" ? ctx : {};
101
- const agentId = firstString(
102
- targetString(params, "_agent_id", "_agentId"),
103
- targetString(params, "agent_id", "agentId"),
104
- contextString(safeCtx, "agentId", "agent_id"),
105
- );
106
- const sessionKey = firstString(
107
- targetString(params, "_session_key", "_sessionKey"),
108
- targetString(params, "session_key", "sessionKey"),
109
- contextString(safeCtx, "sessionKey", "session_key", "sessionId", "session_id"),
110
- );
111
77
  return {
112
78
  notify,
113
79
  text: targetString(params, "text", "text"),
@@ -118,8 +84,8 @@ function buildRequest(params, ctx) {
118
84
  senderId: targetString(params, "sender_id", "senderId"),
119
85
  lobsterId: targetString(params, "lobster_id", "lobsterId"),
120
86
  botId: targetString(params, "bot_id", "botId") || defaultBotID(),
121
- agentId,
122
- sessionKey,
87
+ agentId: targetString(params, "agent_id", "agentId"),
88
+ sessionKey: targetString(params, "session_key", "sessionKey"),
123
89
  physicalReleaseName:
124
90
  targetString(params, "physical_release_name", "physicalReleaseName") || defaultReleaseName(),
125
91
  podName: targetString(params, "pod_name", "podName") || hostname(),
@@ -180,8 +146,8 @@ function requestJSON(path, body) {
180
146
  });
181
147
  }
182
148
 
183
- async function heartbeatNotify(params, ctx) {
184
- const request = buildRequest(params || {}, ctx);
149
+ async function heartbeatNotify(params) {
150
+ const request = buildRequest(params || {});
185
151
  const response = await requestJSON(NOTIFY_PATH, request);
186
152
  const gatewayData = response && typeof response === "object" ? response.data : null;
187
153
  const delivered = gatewayData && typeof gatewayData === "object" ? gatewayData.ok !== false : true;
@@ -189,9 +155,9 @@ async function heartbeatNotify(params, ctx) {
189
155
  ok: delivered,
190
156
  request: {
191
157
  notify: request.notify,
192
- groupId: request.groupId,
158
+ conversationType: request.conversationType,
159
+ conversationId: request.conversationId,
193
160
  agentId: request.agentId,
194
- sessionKey: request.sessionKey,
195
161
  topicKey: request.topicKey,
196
162
  dedupeKey: request.dedupeKey,
197
163
  },
@@ -218,9 +184,43 @@ const parameters = {
218
184
  type: "string",
219
185
  description: "User-visible notification text. Required when notify=true. Do not put this text in the final heartbeat reply.",
220
186
  },
187
+ target_type: {
188
+ type: "string",
189
+ enum: ["direct", "group"],
190
+ description: "Target conversation type. Same as conversation_type.",
191
+ },
192
+ conversation_type: {
193
+ type: "string",
194
+ enum: ["direct", "group"],
195
+ description: "Palz conversation type.",
196
+ },
197
+ conversation_id: {
198
+ type: "string",
199
+ description: "Palz conversation_id to deliver into.",
200
+ },
221
201
  group_id: {
222
202
  type: "string",
223
- description: "Optional group ID for group notifications. Omit for the owner direct chat.",
203
+ description: "Optional Palz group_id for group notifications.",
204
+ },
205
+ sender_id: {
206
+ type: "string",
207
+ description: "Owner/user ID from the selected route.",
208
+ },
209
+ lobster_id: {
210
+ type: "string",
211
+ description: "Bot/lobster ID from the selected route.",
212
+ },
213
+ bot_id: {
214
+ type: "string",
215
+ description: "Optional bot_id. Defaults to current pod botID.",
216
+ },
217
+ agent_id: {
218
+ type: "string",
219
+ description: "Logical main agent ID that produced the heartbeat.",
220
+ },
221
+ session_key: {
222
+ type: "string",
223
+ description: "Optional heartbeat session key for tracing.",
224
224
  },
225
225
  physical_release_name: {
226
226
  type: "string",
@@ -251,45 +251,13 @@ const plugin = {
251
251
  name: "Heartbeat Notify",
252
252
  description: "Deliver structured main-agent heartbeat notifications through claw-gateway.",
253
253
  register(api) {
254
- if (api && typeof api.on === "function") {
255
- api.on("before_tool_call", async (event, ctx) => {
256
- const safeEvent = event && typeof event === "object" ? event : {};
257
- const toolName = trimString(safeEvent.toolName || safeEvent.tool_name || safeEvent.name);
258
- if (toolName !== TOOL_NAME) {
259
- return undefined;
260
- }
261
- const params = safeEvent.params && typeof safeEvent.params === "object" ? safeEvent.params : {};
262
- const safeCtx = ctx && typeof ctx === "object" ? ctx : {};
263
- const nextParams = { ...params };
264
- const agentId = firstString(
265
- targetString(nextParams, "_agent_id", "_agentId"),
266
- targetString(nextParams, "agent_id", "agentId"),
267
- contextString(safeCtx, "agentId", "agent_id"),
268
- contextString(safeEvent, "agentId", "agent_id"),
269
- );
270
- const sessionKey = firstString(
271
- targetString(nextParams, "_session_key", "_sessionKey"),
272
- targetString(nextParams, "session_key", "sessionKey"),
273
- contextString(safeCtx, "sessionKey", "session_key", "sessionId", "session_id"),
274
- contextString(safeEvent, "sessionKey", "session_key", "sessionId", "session_id"),
275
- );
276
- if (agentId) {
277
- nextParams._agent_id = agentId;
278
- }
279
- if (sessionKey) {
280
- nextParams._session_key = sessionKey;
281
- }
282
- return { params: nextParams };
283
- });
284
- }
285
-
286
254
  api.registerTool({
287
255
  name: TOOL_NAME,
288
256
  description:
289
- "Send or no-op a main-agent heartbeat notification. Use only from HEARTBEAT.md. If notify=true, provide text and optional group_id only; omit group_id for the owner direct chat. Gateway resolves sender, bot, lobster, and conversation route. If target is missing or uncertain, call with notify=false. After this tool call, the final heartbeat reply must still be exactly HEARTBEAT_OK.",
257
+ "Send or no-op a main-agent heartbeat notification. Use only from HEARTBEAT.md. If notify=true, provide text plus conversation_type, conversation_id, sender_id, lobster_id, and bot_id/current botID. If target is missing or uncertain, call with notify=false. After this tool call, the final heartbeat reply must still be exactly HEARTBEAT_OK.",
290
258
  parameters,
291
- async execute(_callId, params, _signal, _onUpdate, ctx) {
292
- return toolResult(await heartbeatNotify(params || {}, ctx));
259
+ async execute(_callId, params) {
260
+ return toolResult(await heartbeatNotify(params || {}));
293
261
  },
294
262
  });
295
263
  },
@@ -297,9 +265,7 @@ const plugin = {
297
265
 
298
266
  plugin.__test = {
299
267
  buildRequest,
300
- contextString,
301
268
  defaultBotID,
302
- firstString,
303
269
  gatewayURL,
304
270
  heartbeatNotify,
305
271
  requestJSON,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heartbeat-notify",
3
- "version": "1.0.1",
3
+ "version": "2026.7.1",
4
4
  "npm_package": "@hzhipeng/heartbeat-notify",
5
5
  "description": "Heartbeat notify plugin: expose heartbeat_notify as an OpenClaw native tool",
6
6
  "openclaw_config": {
@@ -2,7 +2,7 @@
2
2
  "id": "heartbeat-notify",
3
3
  "name": "Heartbeat Notify",
4
4
  "description": "Native tool for delivering structured main-agent heartbeat notifications through claw-gateway.",
5
- "version": "1.0.1",
5
+ "version": "2026.7.1",
6
6
  "contracts": {
7
7
  "tools": [
8
8
  "heartbeat_notify"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hzhipeng/heartbeat-notify",
3
- "version": "1.0.1",
3
+ "version": "2026.7.1",
4
4
  "description": "OpenClaw main-agent heartbeat notification plugin for Gateway delivery",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -32,39 +32,22 @@ const expectedTools = ["heartbeat_notify"];
32
32
  const req = plugin.__test.buildRequest({
33
33
  notify: true,
34
34
  text: "hello",
35
- group_id: "group-1",
36
- _agent_id: "user-1-main",
37
- _session_key: "agent:user-1-main:heartbeat",
35
+ conversation_type: "direct",
36
+ conversation_id: "conv-1",
37
+ sender_id: "user-1",
38
+ lobster_id: "lobster_1",
39
+ agent_id: "user-1-main",
38
40
  dedupe_key: "topic-1",
39
41
  });
40
42
  assert.strictEqual(req.notify, true);
41
43
  assert.strictEqual(req.botId, "lobster_1");
42
44
  assert.strictEqual(req.podName, "pod-a");
43
- assert.strictEqual(req.groupId, "group-1");
44
- assert.strictEqual(req.agentId, "user-1-main");
45
- assert.strictEqual(req.sessionKey, "agent:user-1-main:heartbeat");
45
+ assert.strictEqual(req.conversationType, "direct");
46
+ assert.strictEqual(req.conversationId, "conv-1");
46
47
  assert.strictEqual(req.dedupeKey, "topic-1");
47
48
  } finally {
48
49
  process.env = oldEnv;
49
50
  }
50
51
  }
51
52
 
52
- {
53
- const req = plugin.__test.buildRequest(
54
- {
55
- notify: true,
56
- text: "hello direct",
57
- },
58
- {
59
- agentId: "user-a0bd-main",
60
- sessionKey: "agent:user-a0bd-main:main",
61
- },
62
- );
63
- assert.strictEqual(req.notify, true);
64
- assert.strictEqual(req.text, "hello direct");
65
- assert.strictEqual(req.agentId, "user-a0bd-main");
66
- assert.strictEqual(req.sessionKey, "agent:user-a0bd-main:main");
67
- assert.strictEqual(req.groupId, "");
68
- }
69
-
70
53
  console.log("heartbeat-notify tests passed");