Skip to content

Commit 51dceab

Browse files
committed
fix(openclaw): restore inbound bridge via history fallback
1 parent a0c9a1e commit 51dceab

File tree

2 files changed

+438
-18
lines changed

2 files changed

+438
-18
lines changed

src/lib/server/connectors/openclaw.test.ts

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ function findReq(ws: MockWebSocket, method: string): WsFrame | undefined {
6161
return ws.sent.find((frame) => frame?.type === 'req' && frame?.method === method)
6262
}
6363

64+
function findReqAt(ws: MockWebSocket, method: string, index: number): WsFrame | undefined {
65+
const matches = ws.sent.filter((frame) => frame?.type === 'req' && frame?.method === method)
66+
return matches[index]
67+
}
68+
6469
async function waitFor<T>(
6570
getValue: () => T | null | undefined,
6671
timeoutMs = 2_000,
@@ -79,6 +84,7 @@ async function bootstrapConnector(params?: {
7984
onMessage?: (msg: any) => Promise<string>
8085
connectorId?: string
8186
wsUrl?: string
87+
config?: Record<string, unknown>
8288
}) {
8389
const connectorId = params?.connectorId || `test-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`
8490
const connector = {
@@ -89,6 +95,8 @@ async function bootstrapConnector(params?: {
8995
credentialId: null,
9096
config: {
9197
wsUrl: params?.wsUrl || 'ws://localhost:18789',
98+
historyPoll: false,
99+
...(params?.config || {}),
92100
},
93101
isEnabled: true,
94102
status: 'running',
@@ -190,6 +198,48 @@ test('openclaw connector suppresses outbound send when NO_MESSAGE is returned',
190198
}
191199
})
192200

201+
test('openclaw connector accepts short session filter for full agent session keys', async () => {
202+
const received: any[] = []
203+
const { instance, ws, identityPath } = await bootstrapConnector({
204+
onMessage: async (msg) => {
205+
received.push(msg)
206+
return 'alias-ok'
207+
},
208+
config: {
209+
sessionKey: 'main',
210+
},
211+
})
212+
213+
try {
214+
await performHandshake(ws)
215+
ws.emit({
216+
type: 'event',
217+
event: 'chat',
218+
payload: {
219+
state: 'final',
220+
sessionKey: 'agent:main:main',
221+
message: { role: 'user', text: 'Hello alias', sender: 'Wayde' },
222+
},
223+
})
224+
225+
const chatReq = await waitFor(() => findReq(ws, 'chat.send'), 2_000)
226+
assert.equal((chatReq.params as any)?.sessionKey, 'agent:main:main')
227+
assert.equal((chatReq.params as any)?.message, 'alias-ok')
228+
assert.equal(received.length, 1)
229+
assert.equal(received[0]?.text, 'Hello alias')
230+
231+
ws.emit({
232+
type: 'res',
233+
id: chatReq.id as string,
234+
ok: true,
235+
payload: { runId: 'alias-run' },
236+
})
237+
} finally {
238+
await instance.stop()
239+
fs.rmSync(identityPath, { force: true })
240+
}
241+
})
242+
193243
test('openclaw connector sendMessage attaches local media payloads', async () => {
194244
const { instance, ws, identityPath } = await bootstrapConnector()
195245
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openclaw-connector-test-'))
@@ -222,6 +272,90 @@ test('openclaw connector sendMessage attaches local media payloads', async () =>
222272
}
223273
})
224274

275+
test('openclaw connector polls chat.history fallback for inbound user messages', async () => {
276+
const received: any[] = []
277+
const { instance, ws, identityPath } = await bootstrapConnector({
278+
onMessage: async (msg) => {
279+
received.push(msg)
280+
return 'history-pong'
281+
},
282+
config: {
283+
sessionKey: 'agent:main:main',
284+
historyPoll: true,
285+
historyPollMs: 500,
286+
},
287+
})
288+
289+
try {
290+
await performHandshake(ws)
291+
292+
const firstHistoryReq = await waitFor(
293+
() => findReqAt(ws, 'chat.history', 0),
294+
2_500,
295+
)
296+
assert.equal((firstHistoryReq.params as any)?.sessionKey, 'agent:main:main')
297+
ws.emit({
298+
type: 'res',
299+
id: firstHistoryReq.id as string,
300+
ok: true,
301+
payload: {
302+
sessionKey: 'agent:main:main',
303+
messages: [
304+
{
305+
role: 'user',
306+
timestamp: 1,
307+
content: [{ type: 'text', text: 'old message' }],
308+
},
309+
],
310+
},
311+
})
312+
313+
await new Promise((resolve) => setTimeout(resolve, 120))
314+
assert.equal(received.length, 0)
315+
316+
const secondHistoryReq = await waitFor(
317+
() => findReqAt(ws, 'chat.history', 1),
318+
3_000,
319+
)
320+
ws.emit({
321+
type: 'res',
322+
id: secondHistoryReq.id as string,
323+
ok: true,
324+
payload: {
325+
sessionKey: 'agent:main:main',
326+
messages: [
327+
{
328+
role: 'user',
329+
timestamp: 1,
330+
content: [{ type: 'text', text: 'old message' }],
331+
},
332+
{
333+
role: 'user',
334+
timestamp: 2,
335+
content: [{ type: 'text', text: 'new message' }],
336+
},
337+
],
338+
},
339+
})
340+
341+
const chatReq = await waitFor(() => findReq(ws, 'chat.send'), 2_000)
342+
assert.equal((chatReq.params as any)?.sessionKey, 'agent:main:main')
343+
assert.equal((chatReq.params as any)?.message, 'history-pong')
344+
assert.equal(received.length, 1)
345+
assert.equal(received[0]?.text, 'new message')
346+
347+
ws.emit({
348+
type: 'res',
349+
id: chatReq.id as string,
350+
ok: true,
351+
payload: { runId: 'history-run' },
352+
})
353+
} finally {
354+
await instance.stop()
355+
fs.rmSync(identityPath, { force: true })
356+
}
357+
})
358+
225359
test('openclaw connector reconnects when tick watchdog detects stale connection', async () => {
226360
const { instance, ws, identityPath } = await bootstrapConnector()
227361

0 commit comments

Comments
 (0)