@2en/clawly-plugins 1.23.0 → 1.23.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.
@@ -274,15 +274,21 @@ describe('shouldSkipPushForMessage', () => {
274
274
  expect(shouldSkipPushForMessage(' NO_REPLY ')).toBe('silent reply')
275
275
  })
276
276
 
277
- test('skips short heartbeat ack', () => {
277
+ test('skips heartbeat ack when HEARTBEAT_OK is the only content', () => {
278
278
  expect(shouldSkipPushForMessage('HEARTBEAT_OK')).toBe('heartbeat ack')
279
- expect(shouldSkipPushForMessage('All good. HEARTBEAT_OK')).toBe('heartbeat ack')
279
+ expect(shouldSkipPushForMessage('HEARTBEAT_OK.')).toBe('heartbeat ack')
280
+ expect(shouldSkipPushForMessage('HEARTBEAT_OK\n')).toBe('heartbeat ack')
280
281
  })
281
282
 
282
- test('skips verbose heartbeat ack ending with HEARTBEAT_OK', () => {
283
+ test('does NOT skip meaningful content ending with HEARTBEAT_OK', () => {
284
+ expect(shouldSkipPushForMessage('All good. HEARTBEAT_OK')).toBeNull()
285
+ expect(shouldSkipPushForMessage('Nothing to report. HEARTBEAT_OK。')).toBeNull()
286
+ })
287
+
288
+ test('does NOT skip verbose heartbeat response with content before HEARTBEAT_OK', () => {
283
289
  const verbose =
284
290
  'The user said hello recently. Looking at HEARTBEAT.md checklist: nothing needs attention. HEARTBEAT_OK'
285
- expect(shouldSkipPushForMessage(verbose)).toBe('heartbeat ack')
291
+ expect(shouldSkipPushForMessage(verbose)).toBeNull()
286
292
  })
287
293
 
288
294
  test('does not skip message mentioning HEARTBEAT_OK mid-text', () => {
@@ -291,10 +297,6 @@ describe('shouldSkipPushForMessage', () => {
291
297
  ).toBeNull()
292
298
  })
293
299
 
294
- test('skips heartbeat ack with non-ASCII trailing punctuation', () => {
295
- expect(shouldSkipPushForMessage('Nothing to report. HEARTBEAT_OK。')).toBe('heartbeat ack')
296
- })
297
-
298
300
  test('skips system prompt leak', () => {
299
301
  expect(
300
302
  shouldSkipPushForMessage('Here is some Conversation info (untrusted metadata) text'),
@@ -356,6 +358,29 @@ describe('offline-push with filtered messages', () => {
356
358
  })
357
359
  })
358
360
 
361
+ test('sends push for meaningful heartbeat response and strips HEARTBEAT_OK from body', async () => {
362
+ const {api, logs, handlers} = createMockApi()
363
+ registerOfflinePush(api)
364
+
365
+ await handlers.get('agent_end')!(
366
+ {
367
+ messages: [
368
+ {
369
+ role: 'assistant',
370
+ content: 'Hey! Just checking in — saw some interesting news today.\n\nHEARTBEAT_OK',
371
+ },
372
+ ],
373
+ },
374
+ {sessionKey: 'agent:clawly:main'},
375
+ )
376
+
377
+ expect(logs).toContainEqual({
378
+ level: 'info',
379
+ msg: expect.stringContaining('notified (session=agent:clawly:main)'),
380
+ })
381
+ expect(lastPushOpts?.body).toBe('Hey! Just checking in — saw some interesting news today.')
382
+ })
383
+
359
384
  test('sends push for normal message text', async () => {
360
385
  const {api, logs, handlers} = createMockApi()
361
386
  registerOfflinePush(api)
@@ -79,8 +79,11 @@ export function shouldSkipPushForMessage(text: string): string | null {
79
79
  // Agent sentinel "nothing to say" — mobile hides as "silentReply"
80
80
  if (trimmed === 'NO_REPLY') return 'silent reply'
81
81
 
82
- // Heartbeat acknowledgment (HEARTBEAT_OK as ending sentinel) — mobile hides as "heartbeatAck"
83
- if (/HEARTBEAT_OK[\p{P}\s]*$/u.test(text)) return 'heartbeat ack'
82
+ // Heartbeat acknowledgment (HEARTBEAT_OK as ending sentinel) — only skip if no substantial content before it
83
+ if (/HEARTBEAT_OK[\p{P}\s]*$/u.test(text)) {
84
+ const stripped = text.replace(/HEARTBEAT_OK[\p{P}\s]*$/u, '').trim()
85
+ if (stripped.length === 0) return 'heartbeat ack'
86
+ }
84
87
 
85
88
  // Agent echoed system prompt metadata — mobile hides as "systemPromptLeak"
86
89
  if (text.includes('Conversation info (untrusted metadata)')) return 'system prompt leak'
@@ -118,8 +121,9 @@ export function registerOfflinePush(api: PluginApi) {
118
121
  return
119
122
  }
120
123
 
121
- const preview = fullText && fullText.length > 140 ? `${fullText.slice(0, 140)}…` : fullText
122
- const body = preview ?? 'Your response is ready'
124
+ const cleaned = fullText?.replace(/HEARTBEAT_OK[\p{P}\s]*$/u, '').trim() ?? null
125
+ const preview = cleaned && cleaned.length > 140 ? `${cleaned.slice(0, 140)}…` : cleaned
126
+ const body = preview || 'Your response is ready'
123
127
 
124
128
  const sent = await sendPushNotification(
125
129
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2en/clawly-plugins",
3
- "version": "1.23.0",
3
+ "version": "1.23.1",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "repository": {