fix: stabilize channel MCP Docker smoke

This commit is contained in:
Peter Steinberger
2026-04-26 11:31:25 +01:00
parent 74a4ff1adc
commit 4506bb2e02
6 changed files with 123 additions and 56 deletions

View File

@@ -33,16 +33,18 @@ async function main() {
});
mcp = mcpHandle.client;
}
const callTool = <T>(params: Parameters<typeof mcp.callTool>[0]) =>
mcp.callTool(params, undefined, { timeout: 240_000 }) as Promise<T>;
const conversation = await waitFor(
"seeded conversation in conversations_list",
async () => {
const listed = (await mcp.callTool({
const listed = await callTool<{
structuredContent?: { conversations?: Array<Record<string, unknown>> };
}>({
name: "conversations_list",
arguments: {},
})) as {
structuredContent?: { conversations?: Array<Record<string, unknown>> };
};
});
return listed.structuredContent?.conversations?.find(
(entry) => entry.sessionKey === "agent:main:main",
);
@@ -52,33 +54,40 @@ async function main() {
assert(conversation.channel === "imessage", "expected seeded channel");
assert(conversation.to === "+15551234567", "expected seeded target");
const fetched = (await mcp.callTool({
name: "conversation_get",
arguments: { session_key: "agent:main:main" },
})) as {
const fetched = await callTool<{
structuredContent?: { conversation?: Record<string, unknown> };
isError?: boolean;
};
}>({
name: "conversation_get",
arguments: { session_key: "agent:main:main" },
});
assert(!fetched.isError, "conversation_get should succeed");
assert(
fetched.structuredContent?.conversation?.sessionKey === "agent:main:main",
"conversation_get returned wrong session",
);
let lastHistory: unknown;
const messages = await waitFor(
"seeded transcript messages",
async () => {
const history = (await mcp.callTool({
const history = await callTool<{
structuredContent?: { messages?: Array<Record<string, unknown>> };
}>({
name: "messages_read",
arguments: { session_key: "agent:main:main", limit: 10 },
})) as {
structuredContent?: { messages?: Array<Record<string, unknown>> };
};
});
lastHistory = history;
const currentMessages = history.structuredContent?.messages ?? [];
return currentMessages.length >= 2 ? currentMessages : undefined;
},
240_000,
);
).catch((error) => {
throw new Error(
`timeout waiting for seeded transcript messages: ${JSON.stringify(lastHistory, null, 2)}`,
{ cause: error },
);
});
await waitFor(
"seeded attachment message",
() =>
@@ -91,13 +100,13 @@ async function main() {
240_000,
);
const attachments = (await mcp.callTool({
name: "attachments_fetch",
arguments: { session_key: "agent:main:main", message_id: "msg-attachment" },
})) as {
const attachments = await callTool<{
structuredContent?: { attachments?: Array<Record<string, unknown>> };
isError?: boolean;
};
}>({
name: "attachments_fetch",
arguments: { session_key: "agent:main:main", message_id: "msg-attachment" },
});
assert(!attachments.isError, "attachments_fetch should succeed");
assert(
(attachments.structuredContent?.attachments?.length ?? 0) === 1,
@@ -105,16 +114,16 @@ async function main() {
);
const waited = (await Promise.all([
mcp.callTool({
callTool<{
structuredContent?: { event?: Record<string, unknown> };
}>({
name: "events_wait",
arguments: {
session_key: "agent:main:main",
after_cursor: 0,
timeout_ms: 10_000,
},
}) as Promise<{
structuredContent?: { event?: Record<string, unknown> };
}>,
}),
gateway.request("chat.inject", {
sessionKey: "agent:main:main",
message: "assistant live event",
@@ -129,12 +138,12 @@ async function main() {
assert(assistantEvent.text === "assistant live event", "expected assistant event text");
const assistantCursor = typeof assistantEvent.cursor === "number" ? assistantEvent.cursor : 0;
const polled = (await mcp.callTool({
const polled = await callTool<{
structuredContent?: { events?: Array<Record<string, unknown>> };
}>({
name: "events_poll",
arguments: { session_key: "agent:main:main", after_cursor: 0, limit: 10 },
})) as {
structuredContent?: { events?: Array<Record<string, unknown>> };
};
});
assert(
(polled.structuredContent?.events ?? []).some(
(entry) => entry.text === "assistant live event",
@@ -144,16 +153,16 @@ async function main() {
const channelMessage = `hello from docker ${randomUUID()}`;
const userEvent = (await Promise.all([
mcp.callTool({
callTool<{
structuredContent?: { event?: Record<string, unknown> };
}>({
name: "events_wait",
arguments: {
session_key: "agent:main:main",
after_cursor: assistantCursor,
timeout_ms: 10_000,
},
}) as Promise<{
structuredContent?: { event?: Record<string, unknown> };
}>,
}),
gateway.request("chat.send", {
sessionKey: "agent:main:main",
message: channelMessage,

View File

@@ -26,7 +26,8 @@ docker run --rm \
-e "OPENCLAW_SKIP_GMAIL_WATCHER=1" \
-e "OPENCLAW_SKIP_CRON=1" \
-e "OPENCLAW_SKIP_CANVAS_HOST=1" \
-e "OPENCLAW_ACPX_RUNTIME_STARTUP_PROBE=1" \
-e "OPENCLAW_SKIP_ACPX_RUNTIME=1" \
-e "OPENCLAW_SKIP_ACPX_RUNTIME_PROBE=1" \
-e "OPENCLAW_STATE_DIR=/tmp/openclaw-state" \
-e "OPENCLAW_CONFIG_PATH=/tmp/openclaw-state/openclaw.json" \
-e "GW_URL=ws://127.0.0.1:$PORT" \
@@ -50,11 +51,22 @@ docker run --rm \
node --import tsx scripts/e2e/mcp-channels-seed.ts >/tmp/mcp-channels-seed.log
node \"\$entry\" gateway --port $PORT --bind loopback --allow-unconfigured >/tmp/mcp-channels-gateway.log 2>&1 &
gateway_pid=\$!
stop_process() {
pid=\"\$1\"
kill \"\$pid\" >/dev/null 2>&1 || true
for _ in \$(seq 1 40); do
if ! kill -0 \"\$pid\" >/dev/null 2>&1; then
wait \"\$pid\" >/dev/null 2>&1 || true
return
fi
sleep 0.25
done
kill -9 \"\$pid\" >/dev/null 2>&1 || true
wait \"\$pid\" >/dev/null 2>&1 || true
}
cleanup_inner() {
kill \"\$gateway_pid\" >/dev/null 2>&1 || true
wait \"\$gateway_pid\" >/dev/null 2>&1 || true
kill \"\$mock_pid\" >/dev/null 2>&1 || true
wait \"\$mock_pid\" >/dev/null 2>&1 || true
stop_process \"\$gateway_pid\"
stop_process \"\$mock_pid\"
}
dump_gateway_log_on_error() {
status=\$?
@@ -79,19 +91,6 @@ docker run --rm \
tail -n 120 /tmp/mcp-channels-gateway.log 2>/dev/null || true
exit 1
fi
acpx_ready=0
for _ in \$(seq 1 2400); do
if grep -q '\[plugins\] embedded acpx runtime backend ready' /tmp/mcp-channels-gateway.log 2>/dev/null; then
acpx_ready=1
break
fi
sleep 0.25
done
if [ \"\$acpx_ready\" -ne 1 ]; then
echo \"Embedded ACPX runtime did not become ready\"
tail -n 120 /tmp/mcp-channels-gateway.log 2>/dev/null || true
exit 1
fi
node --import tsx scripts/e2e/mcp-channels-docker-client.ts
" >"$CLIENT_LOG" 2>&1
status=${PIPESTATUS[0]}

View File

@@ -388,7 +388,10 @@ export async function maybeApprovePendingBridgePairing(
}>("device.pair.list", {});
} catch (error) {
const message = formatErrorMessage(error);
if (message.includes("missing scope: operator.pairing")) {
if (
message.includes("missing scope: operator.pairing") ||
message.includes("device.pair.list")
) {
return false;
}
throw error;

View File

@@ -23,6 +23,16 @@ async function main() {
enabled: false,
},
},
agents: {
defaults: {
heartbeat: {
every: "0m",
},
},
},
plugins: {
enabled: false,
},
} satisfies OpenClawConfig,
"sk-docker-smoke-test",
);