@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
|
|
277
|
+
test('skips heartbeat ack when HEARTBEAT_OK is the only content', () => {
|
|
278
278
|
expect(shouldSkipPushForMessage('HEARTBEAT_OK')).toBe('heartbeat ack')
|
|
279
|
-
expect(shouldSkipPushForMessage('
|
|
279
|
+
expect(shouldSkipPushForMessage('HEARTBEAT_OK.')).toBe('heartbeat ack')
|
|
280
|
+
expect(shouldSkipPushForMessage('HEARTBEAT_OK\n')).toBe('heartbeat ack')
|
|
280
281
|
})
|
|
281
282
|
|
|
282
|
-
test('
|
|
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)).
|
|
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)
|
package/gateway/offline-push.ts
CHANGED
|
@@ -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) —
|
|
83
|
-
if (/HEARTBEAT_OK[\p{P}\s]*$/u.test(text))
|
|
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
|
|
122
|
-
const
|
|
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
|
{
|