@2en/clawly-plugins 1.24.5 → 1.24.7-beta.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.
|
@@ -120,6 +120,19 @@ describe('offline-push', () => {
|
|
|
120
120
|
expect(logs.filter((l) => l.msg.includes('notified'))).toHaveLength(0)
|
|
121
121
|
})
|
|
122
122
|
|
|
123
|
+
test('sends push for cron session', async () => {
|
|
124
|
+
const {api, logs, handlers} = createMockApi()
|
|
125
|
+
registerOfflinePush(api)
|
|
126
|
+
|
|
127
|
+
await handlers.get('agent_end')!({}, {sessionKey: 'agent:clawly:cron:weather-check:run:abc123'})
|
|
128
|
+
|
|
129
|
+
expect(logs).toContainEqual({
|
|
130
|
+
level: 'info',
|
|
131
|
+
msg: expect.stringContaining('notified (session=agent:clawly:cron:weather-check:run:abc123)'),
|
|
132
|
+
})
|
|
133
|
+
expect(logs.filter((l) => l.msg.includes('skipped'))).toHaveLength(0)
|
|
134
|
+
})
|
|
135
|
+
|
|
123
136
|
test('sends push when ctx is missing', async () => {
|
|
124
137
|
const {api, logs, handlers} = createMockApi()
|
|
125
138
|
registerOfflinePush(api)
|
|
@@ -188,6 +201,52 @@ describe('offline-push', () => {
|
|
|
188
201
|
|
|
189
202
|
expect(lastPushOpts?.body).toBe('Your response is ready')
|
|
190
203
|
})
|
|
204
|
+
|
|
205
|
+
test('body strips [[type:value]] placeholders', async () => {
|
|
206
|
+
const {api, handlers} = createMockApi()
|
|
207
|
+
registerOfflinePush(api)
|
|
208
|
+
|
|
209
|
+
await handlers.get('agent_end')!(
|
|
210
|
+
{
|
|
211
|
+
messages: [
|
|
212
|
+
{role: 'assistant', content: 'Here is your audio [[tts:/res/audio.mp3]] enjoy!'},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{sessionKey: 'agent:clawly:main'},
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
expect(lastPushOpts?.body).toBe('Here is your audio enjoy!')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test('body strips bare [[word]] placeholders', async () => {
|
|
222
|
+
const {api, handlers} = createMockApi()
|
|
223
|
+
registerOfflinePush(api)
|
|
224
|
+
|
|
225
|
+
await handlers.get('agent_end')!(
|
|
226
|
+
{
|
|
227
|
+
messages: [
|
|
228
|
+
{role: 'assistant', content: 'Got it [[reply_to_current]] I will remember that.'},
|
|
229
|
+
],
|
|
230
|
+
},
|
|
231
|
+
{sessionKey: 'agent:clawly:main'},
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
expect(lastPushOpts?.body).toBe('Got it I will remember that.')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test('body strips MEDIA:xxx placeholders', async () => {
|
|
238
|
+
const {api, handlers} = createMockApi()
|
|
239
|
+
registerOfflinePush(api)
|
|
240
|
+
|
|
241
|
+
await handlers.get('agent_end')!(
|
|
242
|
+
{
|
|
243
|
+
messages: [{role: 'assistant', content: 'Check this out MEDIA:/res/image.png pretty cool'}],
|
|
244
|
+
},
|
|
245
|
+
{sessionKey: 'agent:clawly:main'},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
expect(lastPushOpts?.body).toBe('Check this out pretty cool')
|
|
249
|
+
})
|
|
191
250
|
})
|
|
192
251
|
|
|
193
252
|
// ── getLastAssistantText / getLastAssistantPreview unit tests ────
|
|
@@ -303,6 +362,15 @@ describe('shouldSkipPushForMessage', () => {
|
|
|
303
362
|
).toBe('system prompt leak')
|
|
304
363
|
})
|
|
305
364
|
|
|
365
|
+
test('skips system message leak', () => {
|
|
366
|
+
expect(shouldSkipPushForMessage('[System Message] Agent orchestration update')).toBe(
|
|
367
|
+
'system message leak',
|
|
368
|
+
)
|
|
369
|
+
expect(shouldSkipPushForMessage('Some text with [System Message] embedded')).toBe(
|
|
370
|
+
'system message leak',
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
|
|
306
374
|
test('does not skip normal assistant messages', () => {
|
|
307
375
|
expect(shouldSkipPushForMessage('Hey! How can I help you?')).toBeNull()
|
|
308
376
|
expect(shouldSkipPushForMessage('Here is the weather report for today.')).toBeNull()
|
package/gateway/offline-push.ts
CHANGED
|
@@ -13,6 +13,11 @@ import type {PluginApi} from '../types'
|
|
|
13
13
|
import {sendPushNotification} from './notification'
|
|
14
14
|
import {isClientOnline} from './presence'
|
|
15
15
|
|
|
16
|
+
/** Strip [[type:value]], [[word]], and MEDIA:xxx placeholders from text (canonical: apps/mobile/lib/stripPlaceholders.ts). */
|
|
17
|
+
function stripPlaceholders(text: string): string {
|
|
18
|
+
return text.replace(/\[\[\w+(?::[^\]]+)?\]\]|MEDIA:\S+/g, '').trim()
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
/**
|
|
17
22
|
* Extract the last assistant message's full text from a messages array.
|
|
18
23
|
* Handles both string and `{type:'text', text}[]` content formats.
|
|
@@ -88,6 +93,9 @@ export function shouldSkipPushForMessage(text: string): string | null {
|
|
|
88
93
|
// Agent echoed system prompt metadata — mobile hides as "systemPromptLeak"
|
|
89
94
|
if (text.includes('Conversation info (untrusted metadata)')) return 'system prompt leak'
|
|
90
95
|
|
|
96
|
+
// Agent orchestration messages leaking into chat — mobile hides as "systemMessageLeak"
|
|
97
|
+
if (text.includes('[System Message]')) return 'system message leak'
|
|
98
|
+
|
|
91
99
|
return null
|
|
92
100
|
}
|
|
93
101
|
|
|
@@ -114,14 +122,19 @@ export function registerOfflinePush(api: PluginApi) {
|
|
|
114
122
|
const sessionKey = typeof ctx?.sessionKey === 'string' ? ctx.sessionKey : undefined
|
|
115
123
|
const agentId = typeof ctx?.agentId === 'string' ? ctx.agentId : undefined
|
|
116
124
|
|
|
117
|
-
// Only send push for the main clawly mobile session
|
|
118
|
-
// sessions (telegram, slack, discord, etc.) which have their own delivery.
|
|
119
|
-
if (
|
|
125
|
+
// Only send push for the main clawly mobile session and cron sessions —
|
|
126
|
+
// skip channel sessions (telegram, slack, discord, etc.) which have their own delivery.
|
|
127
|
+
if (
|
|
128
|
+
sessionKey !== undefined &&
|
|
129
|
+
sessionKey !== 'agent:clawly:main' &&
|
|
130
|
+
!sessionKey.startsWith('agent:clawly:cron:')
|
|
131
|
+
) {
|
|
120
132
|
api.logger.info(`offline-push: skipped (non-main session: ${sessionKey})`)
|
|
121
133
|
return
|
|
122
134
|
}
|
|
123
135
|
|
|
124
|
-
const
|
|
136
|
+
const noHeartbeat = fullText?.replace(/HEARTBEAT_OK[\p{P}\s]*$/u, '').trim() ?? null
|
|
137
|
+
const cleaned = noHeartbeat ? stripPlaceholders(noHeartbeat) : null
|
|
125
138
|
const preview = cleaned && cleaned.length > 140 ? `${cleaned.slice(0, 140)}…` : cleaned
|
|
126
139
|
const body = preview || 'Your response is ready'
|
|
127
140
|
|
package/gateway/plugins.ts
CHANGED
|
@@ -22,6 +22,7 @@ const PLUGIN_SENTINEL = 'openclaw.plugin.json'
|
|
|
22
22
|
interface NpmViewCache {
|
|
23
23
|
version: string
|
|
24
24
|
versions: string[]
|
|
25
|
+
distTags: Record<string, string>
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const npmCache = new LruCache<NpmViewCache>({maxSize: 5, ttlMs: 5 * 60 * 1000})
|
|
@@ -50,6 +51,10 @@ async function fetchNpmView(npmPkgName: string): Promise<NpmViewCache | null> {
|
|
|
50
51
|
const entry: NpmViewCache = {
|
|
51
52
|
version: typeof parsed.version === 'string' ? parsed.version : '',
|
|
52
53
|
versions: Array.isArray(parsed.versions) ? parsed.versions : [],
|
|
54
|
+
distTags:
|
|
55
|
+
parsed['dist-tags'] && typeof parsed['dist-tags'] === 'object'
|
|
56
|
+
? (parsed['dist-tags'] as Record<string, string>)
|
|
57
|
+
: {},
|
|
53
58
|
}
|
|
54
59
|
npmCache.set(npmPkgName, entry)
|
|
55
60
|
return entry
|
|
@@ -144,6 +149,7 @@ export function registerPlugins(api: PluginApi) {
|
|
|
144
149
|
api.logger.info(`plugins.version: checking ${pluginId} (${npmPkgName})`)
|
|
145
150
|
let latestNpmVersion: string | null = null
|
|
146
151
|
let allNpmVersions: string[] = []
|
|
152
|
+
let npmDistTags: Record<string, string> = {}
|
|
147
153
|
let updateAvailable = false
|
|
148
154
|
const errors: string[] = []
|
|
149
155
|
|
|
@@ -157,6 +163,7 @@ export function registerPlugins(api: PluginApi) {
|
|
|
157
163
|
if (npm) {
|
|
158
164
|
latestNpmVersion = npm.version
|
|
159
165
|
allNpmVersions = npm.versions
|
|
166
|
+
npmDistTags = npm.distTags
|
|
160
167
|
api.logger.info(
|
|
161
168
|
`plugins.version: npm registry latest=${latestNpmVersion}, ${allNpmVersions.length} versions available`,
|
|
162
169
|
)
|
|
@@ -182,6 +189,7 @@ export function registerPlugins(api: PluginApi) {
|
|
|
182
189
|
npmPackageVersion,
|
|
183
190
|
latestNpmVersion,
|
|
184
191
|
allNpmVersions,
|
|
192
|
+
npmDistTags,
|
|
185
193
|
updateAvailable,
|
|
186
194
|
...(errors.length ? {error: errors.join('; ')} : {}),
|
|
187
195
|
})
|